Python使用PyGame模块播放midi音符(一)

2023-11-08 23:50

本文主要是介绍Python使用PyGame模块播放midi音符(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

乐理知识

先铺垫一下基础知识。

BPM

  介绍

BPM为每分钟节拍数,是全曲速度标记,为独立在曲谱外的速度标准,一般以一个四分音符为一拍,60BPM为一分钟演奏均匀60个四分音符(或等效的音符组合)。一般记一个四分音符为一拍,而后描述一拍即为在当前BPM下的一个四分音符。60BPM对应的曲目速度为一分钟均匀演奏60个四分音符(或等效音符组合),即一个四分音符(或等效音符组合)的时值应为1秒,而对应提供演奏者现实的演奏速度。

BPM(每分钟节拍数的单位)_百度百科 (baidu.com)

人话:每分钟四分音符个数

  bpm换算四分音符时长

计算某个BPM值对应的每隔多少秒一拍的公式是

60÷某个BPM值=对应的每隔多少秒一拍

'''
输入bpm,返回四分音符时长
bpm:每分钟节拍数
'''
def bpm_to_quarterNote_duration(bpm):return 60/bpm

 拍号

拍号是在乐谱中使用的符号,用分数的形式来标画。 

分母表示拍子的时值(用几分音符来当一拍),分母代表每一小节有多少拍子。如2/4拍表示以四分音符为一拍,每小节2拍(2个四分音符的时值)

 强弱规律

  介绍

根据每小节的拍子数,可得出一个小节内音符的强拍、次强拍、弱拍,如

  • 42拍是强/弱
  • 43拍是强/弱/弱
  • 44拍是强/弱/次强/弱

并不需要按照这个规律来处理小节中的每一个音,强弱拍只是最能体现出音乐的律动,让人捕捉到音乐开始的拍点,所以把第一拍弹强,符合大多数人的听觉经验和音乐实践。

这篇文章讲的很透彻:

音乐的强弱规律是什么?如何在钢琴演奏中应用强弱拍? - 知乎 (zhihu.com)

  每小节拍子数计算强弱规律

根据每小节拍子数计算每一拍内的强弱规律

'''
根据每小节拍数计算强弱规律
beats_per_measure:每小节拍数,如4表示每小节4拍
2拍:强弱规律为 [1, 0.95]
强弱
3拍:强弱规律为 [1, 0.9, 0.9]
强弱弱
4拍:强弱规律为 [1, 0.9, 0.95, 0.9]
强弱次强弱
5拍:强弱规律为 [1, 0.9, 0.9, 1, 0.9]
强弱弱强弱
6拍:强弱规律为 [1, 0.9, 0.9, 0.95, 0.9, 0.9]
强弱弱次强弱弱
7拍:强弱规律为 [1, 0.9, 0.9, 1, 0.9, 0.9, 1]
强弱弱强弱弱强
8拍:强弱规律为 [1, 0.9, 0.95, 0.9, 1, 0.9, 0.95, 0.9]
强弱次强弱 强弱次强弱
12拍:强弱规律为[1, 0.9, 0.9, 0.95, 0.9, 0.9, 1, 0.9, 0.9, 0.95, 0.9, 0.9]
强弱弱次强弱弱 强弱弱次强弱弱
16拍:强弱规律为[1, 0.9, 0.95, 0.9, 0.95, 0.9, 1, 0.9, 0.95, 0.9, 0.95, 0.9, 1, 0.9, 0.95, 0.9]
强弱次强弱次强弱 强弱次强弱次强弱 强弱次强弱
'''
def meter_to_rhythm(beats_per_measure=4):if   beats_per_measure == 2:return [1, 0.95]elif beats_per_measure == 3:return [1, 0.9, 0.9]elif beats_per_measure == 4:return [1, 0.9, 0.95, 0.9]elif beats_per_measure == 5:return [1, 0.9, 0.9, 1, 0.9]elif beats_per_measure == 6:return [1, 0.9, 0.9, 0.95, 0.9, 0.9]elif beats_per_measure == 7:return [1, 0.9, 0.9, 1, 0.9, 0.9, 1]elif beats_per_measure == 8:return [1, 0.9, 0.95, 0.9, 1, 0.9, 0.95, 0.9]elif beats_per_measure == 12:return [1, 0.9, 0.9, 0.95, 0.9, 0.9, 1, 0.9, 0.9, 0.95, 0.9, 0.9]elif beats_per_measure == 16:return [1, 0.9, 0.95, 0.9, 0.95, 0.9, 1, 0.9, 0.95, 0.9, 0.95, 0.9, 1, 0.9, 0.95, 0.9]else:return [1] * beats_per_measure

音符

 midi音符

在MIDI标准中,音高被编为128个不同的编号,这些编号从0到127,分别表示C0~G10的128个音,其中60号对应中央C(也称为Do)通常被指定为钢琴等音乐器的基准音。

​128位MIDI音高包括对应的音符和频率(hz)赫兹_midi音高对照_laogudong0752的博客-CSDN博客

  简谱音符

数字简谱以可动唱名法为基础,用1、2、3、4、5、6、7代表音阶中的7个音级,休止以0表示。 上方加点表示升八度,下方加点表示降八度。右侧每加一条横线表示延长一拍,下方每加一条横线表示缩短半拍

   简谱音符转换midi音符码
'''
将简谱音符转换为midi音符码
solfeggio:简谱音符,如1, 2, 3, 4, 5, 6, 7, 1#, 2#, 3#, 4#, 5#, 6#, 7#
前面加点表示升八度,后面加点表示降八度,如.1, .2, .3, .4, .5, .6, .7, .1#, .2#, .3#, .4#, .5#, .6#, .7#
'''
def solfeggio_to_midi(solfeggio):if (type(solfeggio) == int): #如果输入的是midi码,则直接返回return solfeggionotes_dict = {'1': 60,'1#': 61,'2b': 61,'2': 62,'2#': 63,'3b': 63,'3': 64,'4': 65,'4#': 66,'5b': 66,'5': 67,'5#': 68,'6b': 68,'6': 69,'7b': 70,'7': 71}octave_shift = 0if solfeggio.endswith('.'):octave_shift -= solfeggio.count('.')solfeggio = solfeggio.rstrip('.')elif solfeggio.startswith('.'):octave_shift += solfeggio.count('.')solfeggio = solfeggio.lstrip('.')if solfeggio not in notes_dict:raise ValueError('Invalid solfeggio note')return notes_dict[solfeggio] + 12*octave_shift

  五线谱音符

以四分音符为一拍:

  • 全音符:空心的白色音符,4拍

  • 二分音符:带符干的白色音符叫,2拍

  • 四分音符:实心的音符,1拍

  • 八分音符:带1个符尾的音符,1/2拍

  • 十六分音符:2符尾,1/4拍

  • 三十二分音符:3符尾,1/8拍

  • 六十四分音符:4符尾,1/16拍

   五线谱音符转换midi音符码

五线谱音符转换为midi音符码需安装music21库

'''
将五线谱音符转换为midi音符码
note:音符名,如C4, A#5
'''
def staffNote_to_midi(note):return music21.note.Note(note).pitch.midi
  midi音符码转换五线谱音符

midi音符码转换为五线谱音符同样使用music21库

'''
将midi音符码转换为五线谱音符
midi:音符的midi码,如60, 69
'''
def midi_to_staffNote(midi):return music21.note.Note(midi).nameWithOctave

五线谱、简谱音符对照表

在音符(包括简谱音符、五线谱音符)右侧加点表示延长这个音的一半,如"1."时值是1.5拍 

曲调

  介绍

曲调决定了音高的升降,如

C--不变

C# / Db--升1个半音

Cb / B--降一个半音

  曲调计算音高升降

输入曲调和音符名,返回根据曲调调整后的音高

'''
输入曲调和音符名,返回根据曲调调整后的音高
tune:曲调,如C, Eb
note:音符名,用midi码表示,如60, 69
'''
def tune_to_semitones(self, note, tune='C'):tunes_dict = {'C': 0,'C#': 1,'Db': 1,'D': 2,'Eb': 3,'E': 4,'F': 5,'F#': 6,'Gb': 6,'G': 7,'Ab': -4,'A': -3,'Bb': -2,'B': -1,'Cb': -1}if tune not in tunes_dict:raise ValueError('Invalid tune')return note + tunes_dict[tune]

 时值

  介绍

音符时值,也称为音符值或音值,在乐谱中用来表达各音符之间的相对持续时间。一个完全音符等于两个二分音符;等于四个四分音符,八个八分音符;十六个十六分音符,三十二个三十二分音符。这只是音符时值的比例。

人话:音符持续时间

音符时值_百度百科 (baidu.com)

  计算音符时长


输入时长(单位:四分音符),返回音符时长(单位:秒)

'''
输入时长(单位:四分音符),返回音符时长(单位:秒)
duration:音符时长(以一个四分音符时长为1)
quaterNote_duration:四分音符时长(秒)
default_duration:默认音符时长(单位:四分音符),当duration为None时,使用默认音符时长
'''
def count_duration(duration, quarterNote_duration, default_noteDuration=1):if duration is None:duration = default_noteDurationreturn quarterNote_duration * duration

 力度

  介绍

力度就是音的强弱程度。在MIDI中,力度值用0~127表示,数字越大力度越大。

力度(音乐名词)_百度百科 (baidu.com)

  力度标记

力度标记通常采用意大利语的音乐术语。作曲家在乐谱上标有详细的力度标记,从最弱的到最强,通常可分为十几个层次,每一个层次的力度都是一个相对值。piano,是弱的意思,缩写为P,P越多就越弱,最多可有5个P,那就是极弱极弱。forte,是强的意思,缩写为f,f越多就越强,假如乐谱上标有五个f,那就是相当强,演奏者必须竭尽全力地演奏。除了这些记号以外,还有很象是数学中的小于号和大于号的渐强和渐弱记号,以及突强突弱记号等等。

   力度标记转换midi力度值

根据基础力度、强弱规律、力度倍数计算力度仅支持fff~ppp

'''
根据基础力度、强弱规律、力度倍数计算力度
base_dynamics:基本力度,默认为16
rhythm:强弱规律,列表
rhythm_count:计数器,用于循环强弱规律
dynamics_multi:力度倍数
'''
def count_dynamics(dynamics, rhythm, base_dynamics=16):global rhythm_countif type(dynamics) == int: #如果输入的是力度值,则直接返回return dynamicsif   dynamics == 'fff':dynamics_mul = 8elif dynamics == 'ff':dynamics_mul = 7elif dynamics == 'f':dynamics_mul = 6elif dynamics == 'mf':dynamics_mul = 5elif dynamics == 'mp':dynamics_mul = 4elif dynamics == 'p':dynamics_mul = 3elif dynamics == 'pp':dynamics_mul = 2elif dynamics == 'ppp':dynamics_mul = 1else:dynamics_mul = 8dynamics = int((base_dynamics*rhythm[rhythm_count]*dynamics_mul) - 1)self.rhythm_count += 1if rhythm_count >= len(rhythm):rhythm_count = 0return dynamics


 

 音色

  介绍

音色又称为音品。为什么音色不同?是由于不同的振动总是可组合成为不同的声音。每一种乐器、不同的人的声带,以及其它所有的能振动的物体都能够发出各有特色的不同的声音,不同的发声体由于其材料、结构不同,则发出声音的音色也不同。

  midi中的各种音色

在MIDI中,一共有128种不同的音色(不包括打击乐器),用0~127中的整数表示

   乐器名转换midi乐器id

将乐器名转换为midi乐器id,支持中英文 

MIDI 128种音色码表_midi音色表_ruyulin的博客-CSDN博客

需安装re库

'''
将乐器名转换为midi乐器id
支持中文、英文、数字
范围:0-127
返回:第一个值为midi乐器id,第二个值为列表中对应乐器名称
midi音色列表:https://www.360docs.net/doc/632018825.htmlhttps://blog.csdn.net/ruyulin/article/details/84103186
'''
def instruments(self, instrument):#定义一个列表,用于存储乐器名称instruments = ["Acoustic Grand Piano 大钢琴","Bright Acoustic Piano 亮音钢琴","Electric Grand Piano 大电钢琴","Honky-tonk Piano 酒吧钢琴","Electric Piano 1 电钢琴1","Electric Piano 2 电钢琴2","Harpsichord 大键琴","Clavinet 电翼琴","Celesta 钢片琴","Glockenspiel 钟琴","Musical box 音乐盒","Vibraphone 颤音琴","Marimba 马林巴琴","Xylophone 木琴","Tubular Bell 管钟","Dulcimer 洋琴","Drawbar Organ 音栓风琴","Percussive Organ 敲击风琴","Rock Organ 摇滚风琴","Church organ 教堂管风琴","Reed organ 簧风琴","Accordion 手风琴","Harmonica 口琴","Tango Accordion 探戈手风琴","Acoustic Guitar nylon 木吉他 尼龙弦","Acoustic Guitar steel 木吉他 钢弦","Electric Guitar jazz 电吉他 爵士","Electric Guitar clean 电吉他 清音","Electric Guitar muted 电吉他 闷音","Overdriven Guitar 电吉他 驱动音效","Distortion Guitar 电吉他 失真音效","Guitar harmonics 吉他泛音","Acoustic Bass 贝斯","Electric Bass finger 电贝斯 指奏","Electric Bass pick 电贝斯 拨奏","Fretless Bass 无品贝斯","Slap Bass 1 捶鈎贝斯","Slap Bass 2 捶鈎贝斯","Synth Bass 1 合成贝斯1","Synth Bass 2 合成贝斯2","Violin 小提琴","Viola 中提琴","Cello 大提琴","Contrabass 低音大提琴","Tremolo Strings 颤弓弦乐","Pizzicato Strings 弹拨弦乐","Orchestral Harp 竖琴","Timpani 定音鼓","String Ensemble 1 弦乐合奏1","String Ensemble 2 弦乐合奏2","Synth Strings 1 合成弦乐1","Synth Strings 2 合成弦乐2","Voice Aahs 人声“啊”","Voice Oohs 人声“喔”","Synth Voice 合成人声","Orchestra Hit 交响打击乐","Trumpet 小号","Trombone 长号","Tuba 大号 吐巴号、低音号","Muted Trumpet 闷音小号","French horn 法国号 圆号","Brass Section 铜管乐","Synth Brass 1 合成铜管1","Synth Brass 2 合成铜管2","Soprano Sax 高音萨克斯风","Alto Sax 中音萨克斯风","Tenor Sax 次中音萨克斯风","Baritone Sax 上低音萨克斯风","Oboe 双簧管","English Horn 英国管","Bassoon 巴松管","Clarinet 单簧管","Piccolo 短笛","Flute 长笛","Recorder 直笛","Pan Flute 排箫","Blown Bottle 吹瓶","Shakuhachi 日本尺八","Whistle 口哨","Ocarina 奥卡雷那","Lead 1 (square) 合成主音1(方波)","Lead 2 (sawtooth) 合成主音2(锯齿波)","Lead 3 (calliope) 合成主音3(汽笛音)","Lead 4 (chiff) 合成主音4(吹管音)","Lead 5 (charang) 合成主音5(电吉他合音)","Lead 6 (voice) 合成主音6(人声主音)","Lead 7 (fifths) 合成主音7(五度音)","Lead 8 (bass + lead) 合成主音8(贝司主音)","Pad 1 (new age) 合成音色1(新世纪)","Pad 2 (warm) 合成音色2(温暖)","Pad 3 (polysynth) 合成音色3(多音合成器)","Pad 4 (choir) 合成音色4(合唱团)","Pad 5 (bowed) 合成音色5(拉弦音色)","Pad 6 (metallic) 合成音色6(金属音色)","Pad 7 (halo) 合成音色7(光环音色)","Pad 8 (sweep) 合成音色8(扫掠音色)","FX 1 (rain) 特殊音色1(下雨声)","FX 2 (soundtrack) 特殊音色2(电影音效)","FX 3 (crystal) 特殊音色3(水晶音色)","FX 4 (atmosphere) 特殊音色4(氛围音色)","FX 5 (brightness) 特殊音色5(明亮音色)","FX 6 (goblins) 特殊音色6(鬼怪音色)","FX 7 (echoes) 特殊音色7(回音音色)","FX 8 (sci-fi) 特殊音色8(科幻音色)","Sitar 西塔尔","Banjo 班卓琴","Shamisen 三味线","Koto 十三弦琴","Kalimba 卡林巴","Bagpipe 风笛","Fiddle 古提琴","Shanai 善艾管","Tinkle Bell 叮当铃","Agogo 音乐杯","Steel Drums 钢鼓","Woodblock 木鱼","Taiko Drum 太鼓","Melodic Tom 旋律定音筒鼓","Synth Drum 合成鼓","Reverse Cymbal 反向钹","Guitar Fret Noise 吉他品格噪音","Breath Noise 呼吸噪音","Seashore 海岸","Bird Tweet 鸟叫","Telephone Ring 电话铃声","Helicopter 直升机","Applause 鼓掌","Gunshot 枪声"]if type(instrument) == int: #如果输入的是midi乐器id,则不变if instrument > 127 or instrument < 0:#乐器id超出范围,抛出异常raise ValueError(f'Invalid instrument: {instrument}')return instrument, instruments[instrument]#将乐器名称转换为小写,并去除空格和标点符号instrument = re.sub(r'[^\w\s]', '', instrument.lower().replace(" ", ""))for i in instruments: #遍历乐器名称列表#转换为小写,并去除空格和标点符号_i = re.sub(r'[^\w\s]', '', i.lower().replace(" ", ""))#如果乐器名称在列表中,则返回对应的midi乐器idif instrument in _i:return instruments.index(i), i#不存在的乐器名称,抛出异常raise ValueError(f'Invalid instrument: {instrument}')

 识谱

  五线谱

  简谱 

 

【音乐课】认识五线谱 (qq.com)

 滑音、颤音

模块使用

用到的模块: pygame.midi、re、music21、time、threading

 安装

pip install pygame
pip install music21

  导入

#导入pygame.midi模块,用于播放midi音乐
from  pygame.midi import *
import pygame.midi
import re #导入re模块,用于正则表达式
import music21 #导入music21模块,用于音乐分析
from time import sleep #导入sleep函数,用于等待一定时间
from threading import Thread #导入Thread类,用于多线程播放音乐

 pygame.midi

#导入pygame.midi模块,用于播放midi音乐
from  pygame.midi import *
import pygame.midi#初始化
pygame.midi.init() #初始化pygame.midi
player = Output(device_id) #设置输出设备,默认设备id为0
player.set_instrument(self.instrument_id, channel) #设置乐器,0~127#播放音符
player.note_on(note, dynamics_value) #播放音符
sleep(duration) #等待一定时长
player.note_off(note, dynamics_value) #停止播放音符#关闭
player.close()
pygame.midi.quit()

 PyGame Python上的MIDI库|极客笔记

 threading

from threading import Threadt = Thread(name="print", target=print, args=('ab', '\n', '1')) #新线程
t.start() #启动线程
t.join() #阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout(可选参数)

python的threading模块_python threading模块_Ernestjackson的博客-CSDN博客

 time

from time import sleepprint("a")
sleep(2) #延时2秒
print("b")

python的time库详解_python time_蕾峰的博客-CSDN博客

正式开干

三个类:MusicConvertPrintMusicInfoMidiMusic

注意:MidiMusic类需要继承另外两个类,并且三个类的方法、变量需统一命名

 MusicConvert

转换音乐数据

先将第一部分写的函数整合成一个类,并在cvt_init函数调用时根据传入的音乐信息设置基础力度、默认音符时长、乐器、曲调、拍号、强弱规律、八分音符时长等:

'''
转换音乐数据
转换音符名、midi码、简谱音符
分析曲调、bpm、拍号
计算音符时长、力度
'''
class MusicConvert:def cvt_init(self, tune='C', bpm=120, signature=[4,4], instrument="piano", default_noteDuration=1, base_dynamics=16):self.rhythm_count = 0 #计数器,用于循环强弱规律self.base_dynamics = base_dynamics #设置基础力度self.default_noteDuration = default_noteDuration #设置默认音符时长self.instrument_id, self.instrument_name = self.instruments(instrument) #将乐器名转换为midi乐器idself.tune = tune #设置曲调self.signature = signature #设置拍号self.rhythm = self.meter_to_rhythm(signature[0]) #设置强弱规律self.quarterNote_duration = self.bpm_to_quarterNote_duration(bpm) #设置八分音符时长'''将乐器名转换为midi乐器id支持中文、英文、数字范围:0-127返回:第一个值为midi乐器id,第二个值为列表中对应乐器名称midi音色列表:https://www.360docs.net/doc/632018825.htmlhttps://blog.csdn.net/ruyulin/article/details/84103186'''def instruments(self, instrument):#定义一个列表,用于存储乐器名称instruments = ["Acoustic Grand Piano 大钢琴 声学钢琴","Bright Acoustic Piano 亮音钢琴","Electric Grand Piano 大电钢琴","Honky-tonk Piano 酒吧钢琴","Electric Piano 1 电钢琴1","Electric Piano 2 电钢琴2","Harpsichord 大键琴","Clavinet 电翼琴","Celesta 钢片琴","Glockenspiel 钟琴","Musical box 音乐盒","Vibraphone 颤音琴","Marimba 马林巴琴","Xylophone 木琴","Tubular Bell 管钟","Dulcimer 洋琴","Drawbar Organ 音栓风琴","Percussive Organ 敲击风琴","Rock Organ 摇滚风琴","Church organ 教堂管风琴","Reed organ 簧风琴","Accordion 手风琴","Harmonica 口琴","Tango Accordion 探戈手风琴","Acoustic Guitar nylon 木吉他 尼龙弦","Acoustic Guitar steel 木吉他 钢弦","Electric Guitar jazz 电吉他 爵士","Electric Guitar clean 电吉他 清音","Electric Guitar muted 电吉他 闷音","Overdriven Guitar 电吉他 驱动音效","Distortion Guitar 电吉他 失真音效","Guitar harmonics 吉他泛音","Acoustic Bass 贝斯 木贝斯 声学贝斯","Electric Bass finger 电贝斯 指奏","Electric Bass pick 电贝斯 拨奏","Fretless Bass 无品贝斯","Slap Bass 1 掌击贝斯1","Slap Bass 2 掌击贝斯2","Synth Bass 1 合成贝斯1","Synth Bass 2 合成贝斯2","Violin 小提琴","Viola 中提琴","Cello 大提琴","Contrabass 低音大提琴","Tremolo Strings 颤弓弦乐","Pizzicato Strings 弹拨弦乐","Orchestral Harp 竖琴","Timpani 定音鼓","String Ensemble 1 弦乐合奏1","String Ensemble 2 弦乐合奏2","Synth Strings 1 合成弦乐1","Synth Strings 2 合成弦乐2","Voice Aahs 人声“啊”","Voice Oohs 人声“喔”","Synth Voice 合成人声","Orchestra Hit 交响打击乐","Trumpet 小号","Trombone 长号","Tuba 大号 吐巴号、低音号","Muted Trumpet 闷音小号","French horn 法国号 圆号","Brass Section 铜管乐","Synth Brass 1 合成铜管1","Synth Brass 2 合成铜管2","Soprano Sax 高音萨克斯风","Alto Sax 中音萨克斯风","Tenor Sax 次中音萨克斯风","Baritone Sax 上低音萨克斯风","Oboe 双簧管","English Horn 英国管","Bassoon 巴松管 大管","Clarinet 单簧管 黑管","Piccolo 短笛","Flute 长笛","Recorder 直笛","Pan Flute 排箫","Blown Bottle 吹瓶","Shakuhachi 日本尺八","Whistle 口哨","Ocarina 奥卡雷那","Lead 1 square 合成主音1 方波","Lead 2 sawtooth 合成主音2 锯齿波","Lead 3 calliope 合成主音3 汽笛音","Lead 4 chiff 合成主音4 吹管音","Lead 5 charang 合成主音5 电吉他合音","Lead 6 voice 合成主音6 人声主音","Lead 7 fifths 合成主音7 五度音","Lead 8 bass + lead 合成主音8 贝司主音","Pad 1 new age 合成音色1 新世纪","Pad 2 warm) 合成音色2 温暖","Pad 3 polysynth 合成音色3 多音合成器","Pad 4 choir 合成音色4 合唱团","Pad 5 bowed 合成音色5 拉弦音色","Pad 6 metallic 合成音色6 金属音色","Pad 7 halo 合成音色7 光环音色","Pad 8 sweep 合成音色8 扫掠音色","FX 1 rain 特殊音色1 下雨声","FX 2 soundtrack 特殊音色2 电影音效","FX 3 crystal 特殊音色3 水晶音色","FX 4 atmosphere 特殊音色4 氛围音色","FX 5 brightness 特殊音色5 明亮音色","FX 6 goblins 特殊音色6 鬼怪音色","FX 7 echoes 特殊音色7 回音音色","FX 8 sci-fi 特殊音色8 科幻音色","Sitar 西塔尔","Banjo 班卓琴","Shamisen 三味线","Koto 十三弦琴","Kalimba 卡林巴","Bagpipe 风笛","Fiddle 古提琴","Shanai 善艾管","Tinkle Bell 叮当铃","Agogo 音乐杯","Steel Drums 钢鼓","Woodblock 木鱼","Taiko Drum 太鼓","Melodic Tom 旋律定音筒鼓","Synth Drum 合成鼓","Reverse Cymbal 反向钹","Guitar Fret Noise 吉他品格噪音","Breath Noise 呼吸噪音","Seashore 海岸","Bird Tweet 鸟叫","Telephone Ring 电话铃","Helicopter 直升机","Applause 鼓掌","Gunshot 枪声"]if type(instrument) == int: #如果输入的是midi乐器id,则不变if instrument > 127 or instrument < 0:#乐器id超出范围,抛出异常raise ValueError(f'Invalid instrument: {instrument}')return instrument, instruments[instrument]#将乐器名称转换为小写,并去除空格和标点符号instrument = re.sub(r'[^\w\s]', '', instrument.lower().replace(" ", ""))for i in instruments: #遍历乐器名称列表#转换为小写,并去除空格和标点符号_i = re.sub(r'[^\w\s]', '', i.lower().replace(" ", ""))#如果乐器名称在列表中,则返回对应的midi乐器idif instrument in _i:return instruments.index(i), i#不存在的乐器名称,抛出异常raise ValueError(f'Invalid instrument: {instrument}')'''将简谱音符转换为midi音符码solfeggio:简谱音符,如1, 2, 3, 4, 5, 6, 7, 1#, 2#, 3#, 4#, 5#, 6#, 7#前面加点表示升八度,后面加点表示降八度,如.1, .2, .3, .4, .5, .6, .7, .1#, .2#, .3#, .4#, .5#, .6#, .7#'''def solfeggio_to_midi(self, solfeggio):if (type(solfeggio) == int): #如果输入的是midi码,则直接返回return solfeggionotes_dict = {'1': 60,'1#': 61,'2b': 61,'2': 62,'2#': 63,'3b': 63,'3': 64,'4': 65,'4#': 66,'5b': 66,'5': 67,'5#': 68,'6b': 68,'6': 69,'7b': 70,'7': 71}octave_shift = 0if solfeggio.endswith('.'):octave_shift -= solfeggio.count('.')solfeggio = solfeggio.rstrip('.')elif solfeggio.startswith('.'):octave_shift += solfeggio.count('.')solfeggio = solfeggio.lstrip('.')if solfeggio not in notes_dict:raise ValueError('Invalid solfeggio note')return notes_dict[solfeggio] + 12*octave_shift'''将五线谱音符转换为midi音符码note:音符名,如C4, A#5'''def staffNote_to_midi(self, note):return music21.note.Note(note).pitch.midi'''将midi音符码转换为五线谱音符midi:音符的midi码,如60, 69'''def midi_to_staffNote(self, midi):return music21.note.Note(midi).nameWithOctave'''将音符名转换为midi音符码,并根据曲调调整音高note:音符名,如C4, A#5'''def note_to_midi(self, note, tune='C'):if type(note) == int: #如果输入的是midi码,则不变passelif note[0].isalpha(): #如果输入的是音符名,则转换为midi码note = self.staffNote_to_midi(note)elif note[0].isdigit() or note[0] == '.': #如果输入的是简谱音符,则转换为midi码note = self.solfeggio_to_midi(note)note = self.tune_to_semitones(note, tune) #根据曲调调整音高return note'''输入bpm,返回四分音符时长bpm:每分钟节拍数'''def bpm_to_quarterNote_duration(self, bpm):return 60/bpm'''根据每小节拍数计算强弱规律beats_per_measure:每小节拍数,如4表示每小节4拍2拍:强弱规律为 [1, 0.95]强弱3拍:强弱规律为 [1, 0.9, 0.9]强弱弱4拍:强弱规律为 [1, 0.9, 0.95, 0.9]强弱次强弱5拍:强弱规律为 [1, 0.9, 0.9, 1, 0.9]强弱弱强弱6拍:强弱规律为 [1, 0.9, 0.9, 0.95, 0.9, 0.9]强弱弱次强弱弱7拍:强弱规律为 [1, 0.9, 0.9, 1, 0.9, 0.9, 1]强弱弱强弱弱强8拍:强弱规律为 [1, 0.9, 0.95, 0.9, 1, 0.9, 0.95, 0.9]强弱次强弱 强弱次强弱12拍:强弱规律为[1, 0.9, 0.9, 0.95, 0.9, 0.9, 1, 0.9, 0.9, 0.95, 0.9, 0.9]强弱弱次强弱弱 强弱弱次强弱弱16拍:强弱规律为[1, 0.9, 0.95, 0.9, 0.95, 0.9, 1, 0.9, 0.95, 0.9, 0.95, 0.9, 1, 0.9, 0.95, 0.9]强弱次强弱次强弱 强弱次强弱次强弱 强弱次强弱'''def meter_to_rhythm(self, beats_per_measure=4):if   beats_per_measure == 2:return [1, 0.95]elif beats_per_measure == 3:return [1, 0.9, 0.9]elif beats_per_measure == 4:return [1, 0.9, 0.95, 0.9]elif beats_per_measure == 5:return [1, 0.9, 0.9, 1, 0.9]elif beats_per_measure == 6:return [1, 0.9, 0.9, 0.95, 0.9, 0.9]elif beats_per_measure == 7:return [1, 0.9, 0.9, 1, 0.9, 0.9, 1]elif beats_per_measure == 8:return [1, 0.9, 0.95, 0.9, 1, 0.9, 0.95, 0.9]elif beats_per_measure == 12:return [1, 0.9, 0.9, 0.95, 0.9, 0.9, 1, 0.9, 0.9, 0.95, 0.9, 0.9]elif beats_per_measure == 16:return [1, 0.9, 0.95, 0.9, 0.95, 0.9, 1, 0.9, 0.95, 0.9, 0.95, 0.9, 1, 0.9, 0.95, 0.9]else:return [1] * beats_per_measure'''根据基础力度、强弱规律、力度倍数计算力度base_dynamics:基本力度,默认为16rhythm:强弱规律,列表rhythm_count:计数器,用于循环强弱规律dynamics_multi:力度倍数'''def count_dynamics(self, dynamics):if type(dynamics) == int: #如果输入的是力度值,则直接返回return dynamicsif   dynamics == 'fff':dynamics_mul = 8elif dynamics == 'ff':dynamics_mul = 7elif dynamics == 'f':dynamics_mul = 6elif dynamics == 'mf':dynamics_mul = 5elif dynamics == 'mp':dynamics_mul = 4elif dynamics == 'p':dynamics_mul = 3elif dynamics == 'pp':dynamics_mul = 2elif dynamics == 'ppp':dynamics_mul = 1else:dynamics_mul = 8dynamics = int((self.base_dynamics*self.rhythm[self.rhythm_count]*dynamics_mul) - 1)self.rhythm_count += 1if self.rhythm_count >= len(self.rhythm):self.rhythm_count = 0return dynamics'''输入时长(单位:四分音符),返回音符时长(单位:秒)duration:音符时长(以一个四分音符时长为1)quaterNote_duration:四分音符时长(秒)default_duration:默认音符时长(单位:四分音符),当duration为None时,使用默认音符时长'''def count_duration(self, duration):if duration is None:duration = self.default_noteDurationreturn self.quarterNote_duration * duration'''输入曲调和音符名,返回根据曲调调整后的音高tune:曲调,如C, Ebnote:音符名,用midi码表示,如60, 69'''def tune_to_semitones(self, note, tune='C'):tunes_dict = {'C': 0,'C#': 1,'Db': 1,'D': 2,'Eb': 3,'E': 4,'F': 5,'F#': 6,'Gb': 6,'G': 7,'Ab': -4,'A': -3,'Bb': -2,'B': -1,'Cb': -1}if tune not in tunes_dict:raise ValueError('Invalid tune')return note + tunes_dict[tune]

PrintMusicInfo

调用prt_init函数设置音乐信息,之后在每次操作时打印音乐详细信息, 包括

  • 乐曲信息

  • 音符信息

  • 和弦信息

  • 休止符信息

  • 踏板信息

'''
打印音乐信息、音符信息、和弦信息、休止符信息、踏板信息
需要传入Midi类的对象、通道
与Midi类的对象的isPrintInfo属性配合使用
'''
class PrintMusicInfo:def prt_init(self, music, channel, isPrintInfo=True):if isPrintInfo:self.note_count = 1 #音符计数器,用于计算音符个数self.chord_count = 1 #和弦计数器,用于计算和弦个数self.rest_count = 1 #休止符计数器,用于计算休止符个数self.pedal_count = 1 #踏板计数器,用于计算踏板操作次数self.tenutoPedal_on = False #延音踏板状态self.softPedal_on = False #弱音踏板状态self.music = music #MidiMusic类的对象self.channel = channel #通道self.isPrintInfo = isPrintInfo #是否打印信息#打印音乐信息def print_musicInfo(self):if self.isPrintInfo:print('曲调:', self.music.tune)print('拍号:', self.music.signature)print('八分音符时长(秒):', self.music.quarterNote_duration)print('基础力度:', self.music.base_dynamics)print('强弱规律:', self.music.rhythm)print('通道:', self.music.channel)print('乐器名:', self.music.instrument_name, '乐器id:', self.music.instrument_id)print('设备id:', self.music.player.device_id)print('设备信息:', get_device_info(self.music.player.device_id))print('\n')#打印音符信息def print_noteInfo(self, note, duration, dynamics_mark, dynamics_value):if self.isPrintInfo:print('第', self.note_count, '个单音')print('音符名:', self.music.midi_to_staffNote(note), 'midi码:', self.music.note_to_midi(note))print('时长(秒):', duration, '时长(四分音符):', duration/self.music.quarterNote_duration)print('力度标记:', dynamics_mark, '力度值:', dynamics_value)print('\n')self.note_count += 1#打印和弦信息def print_chordInfo(self, *notes, duration, dynamics_mark, dynamics_value):if self.isPrintInfo:print(notes)print('第', self.chord_count, '个和弦')print('音符名:', end='')for note in notes:print(self.music.midi_to_staffNote(note), end=' ')print('\nmidi码:', end='')for note in notes:print(self.music.note_to_midi(note), end=' ')print('\n时长(秒):', duration, '时长(四分音符):', duration/self.music.quarterNote_duration)print('力度标记:', dynamics_mark, '力度值:', dynamics_value)print('\n')self.chord_count += 1#打印休止符信息def print_restInfo(self, duration):if self.isPrintInfo:print('第', self.rest_count, '个休止符')print('时长(秒):', duration, '时长(四分音符):', duration/self.music.quarterNote_duration)print('\n')self.rest_count += 1#打印踏板信息def print_pedalInfo(self):if self.isPrintInfo:print('第', self.pedal_count, '次踏板操作')if self.tenutoPedal_on:print('延音踏板:踩下')else:print('延音踏板:松开')if self.softPedal_on:print('弱音踏板:踩下')else:print('弱音踏板:松开')print('\n')self.pedal_count += 1

  MidiMusic

在构造函数内设置音乐信息,可调用play、playChord、rest、tenutoPedal、softPedal、close方法实现演奏midi音乐

详解:

  __init__

   作用

初始化类(创建对象时)

   参数
  • bpm:每分钟节拍数,默认为120

  • tune:曲调,如C, Eb,默认为C

  • signature:拍号,如[4,2],默认为[4,4]

  • instrument:乐器,可用中、英、数字,默认为钢琴(id=0)

这篇关于Python使用PyGame模块播放midi音符(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/373127

相关文章

python管理工具之conda安装部署及使用详解

《python管理工具之conda安装部署及使用详解》这篇文章详细介绍了如何安装和使用conda来管理Python环境,它涵盖了从安装部署、镜像源配置到具体的conda使用方法,包括创建、激活、安装包... 目录pytpshheraerUhon管理工具:conda部署+使用一、安装部署1、 下载2、 安装3

Mysql虚拟列的使用场景

《Mysql虚拟列的使用场景》MySQL虚拟列是一种在查询时动态生成的特殊列,它不占用存储空间,可以提高查询效率和数据处理便利性,本文给大家介绍Mysql虚拟列的相关知识,感兴趣的朋友一起看看吧... 目录1. 介绍mysql虚拟列1.1 定义和作用1.2 虚拟列与普通列的区别2. MySQL虚拟列的类型2

Python进阶之Excel基本操作介绍

《Python进阶之Excel基本操作介绍》在现实中,很多工作都需要与数据打交道,Excel作为常用的数据处理工具,一直备受人们的青睐,本文主要为大家介绍了一些Python中Excel的基本操作,希望... 目录概述写入使用 xlwt使用 XlsxWriter读取修改概述在现实中,很多工作都需要与数据打交

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

关于@MapperScan和@ComponentScan的使用问题

《关于@MapperScan和@ComponentScan的使用问题》文章介绍了在使用`@MapperScan`和`@ComponentScan`时可能会遇到的包扫描冲突问题,并提供了解决方法,同时,... 目录@MapperScan和@ComponentScan的使用问题报错如下原因解决办法课外拓展总结@

mysql数据库分区的使用

《mysql数据库分区的使用》MySQL分区技术通过将大表分割成多个较小片段,提高查询性能、管理效率和数据存储效率,本文就来介绍一下mysql数据库分区的使用,感兴趣的可以了解一下... 目录【一】分区的基本概念【1】物理存储与逻辑分割【2】查询性能提升【3】数据管理与维护【4】扩展性与并行处理【二】分区的

使用Python实现在Word中添加或删除超链接

《使用Python实现在Word中添加或删除超链接》在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能,本文将为大家介绍一下Python如何实现在Word中添加或... 在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能。通过添加超

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存

C#使用HttpClient进行Post请求出现超时问题的解决及优化

《C#使用HttpClient进行Post请求出现超时问题的解决及优化》最近我的控制台程序发现有时候总是出现请求超时等问题,通常好几分钟最多只有3-4个请求,在使用apipost发现并发10个5分钟也... 目录优化结论单例HttpClient连接池耗尽和并发并发异步最终优化后优化结论我直接上优化结论吧,

SpringBoot使用Apache Tika检测敏感信息

《SpringBoot使用ApacheTika检测敏感信息》ApacheTika是一个功能强大的内容分析工具,它能够从多种文件格式中提取文本、元数据以及其他结构化信息,下面我们来看看如何使用Ap... 目录Tika 主要特性1. 多格式支持2. 自动文件类型检测3. 文本和元数据提取4. 支持 OCR(光学