FilePath: \code\python\MIDI\Music.py
LastEditors: Zhu Yiqi
LastEditTime: 2024-02-17 22:18:05
Encoding: UTF-8
Description: A python module used to play midi music
Usage:
from Music import Music
music = Music()
music.play('C')
music.sosPedal()
( ... )
music.close()
from pygame.midi import *#导入pygame.midi模块,用于播放midi音乐
import pygame.midi
import re #导入re模块,用于正则表达式
import music21 #导入music21模块,用于音乐分析
import time #导入time模块,用于计时
from threading import Thread #导入Thread类,用于多线程播放音乐
class Check:
这是一个用于检查踏板状态和开关状态的类。
def check_pedal_state(self, state):
检查踏板的状态, 并返回相应的结果。
- state: 踏板的状态, 可以是布尔值、整数或字符串类型。
- 如果踏板状态为真或者等于 'on' 或者等于 1, 则返回 '踩下'。
- 否则返回 '松开'。
if isinstance(state, str):
state.lower()
if state or state == 'on' or state == 1:
return '踩下'
else:
return '松开'
def check_on_off(self, on_off, original=None):
检查开关的状态, 并返回相应的结果。
- on_off: 开关的状态, 可以是布尔值、整数或字符串类型。
- original: 原始状态, 可选参数。
- 如果开关状态为 'on'、True 或者 1, 则返回 True。
- 如果开关状态为 'off'、False 或者 0, 则返回 False。
- 其他情况下, 返回取反后的 original 值。
if isinstance(on_off, str): #如果传入的是字符串
on_off = on_off.lower() #将字符串转换为小写
if on_off == 'on' or on_off == True or on_off == 1:
return True
elif on_off == 'off' or on_off == False or on_off == 0:
return False
else:
return not original
class PrintMusicInfo:
打印音乐信息、音符信息、和弦信息、休止符信息、踏板信息
需要传入Midi类的对象、通道
与Midi类的对象的isPrintInfo属性配合使用
这是一个用于打印音乐信息的类, 包含了打印音乐的各种元素的方法。
def prt_init(self, music, channel, tune, isPrintInfo=True):
初始化打印音乐信息的对象。
- music: MidiMusic类的对象, 表示要打印信息的音乐。
- channel: 通道, 表示音乐所在的通道。
- isPrintInfo: 是否打印信息, 默认为True。
返回值: 无。
self.note_count = 0 #音符计数器,用于计算音符个数
self.chord_count = 0 #和弦计数器,用于计算和弦个数
self.rest_count = 0 #休止符计数器,用于计算休止符个数
self.pedal_count = 0 #踏板计数器,用于计算踏板操作次数
self.music = music #MidiMusic类的对象
self.ctrl = MidiControl #MidiControl类的对象
self.channel = channel #通道
self.tune = tune #曲调
self.isPrintInfo = isPrintInfo #是否打印信息
def print_musicInfo(self):
打印音乐的基本信息。
参数: 无。
返回值: 无。
if self.isPrintInfo:
print('---音乐信息---')
print('曲调:', self.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.output.device_id)
print('设备信息:', get_device_info(self.music.output.device_id))
print('\n')
print('---音乐开始---')
self.start_time = time.time() #记录开始时间
def print_noteInfo(self, note, duration, dynamics_value):
打印音符的详细信息。
- note: 音符。
- duration: 音符的时长。
- dynamics_mark: 力度标记。
- dynamics_value: 力度值。
返回值: 无。
self.note_count += 1
if self.isPrintInfo:
print('第', self.note_count, '个单音')
print('音符:', note, '--- midi码:', self.music.note2midi(note))
print('时长(四分音符):', duration/self.music.quarterNote_duration, '--- 时长(秒):', duration)
print('力度标记:', self.music.dynamics_val2mark(dynamics_value), ' --- 力度值:', dynamics_value)
print('\n')
def print_chordInfo(self, duration, dynamics_mark, dynamics_value, *notes):
打印和弦的详细信息。
- duration: 和弦的时长。
- dynamics_mark: 力度标记。
- dynamics_value: 力度值。
- *notes: 和弦中的音符。
返回值: 无。
self.chord_count += 1
if self.isPrintInfo:
print(notes)
print('第', self.chord_count, '个和弦')
print('音符:', notes, sep='\n')
midis=[]
for note in notes:
midis.append(self.music.note2midi(note))
print('midi码:', midis, sep='\n')
print('时长(秒):', duration, '--- 时长(四分音符): ', duration/self.music.quarterNote_duration)
print('力度标记:', dynamics_mark, '--- 力度值:', dynamics_value)
print('\n')
#打印休止符信息
def print_restInfo(self, duration):
打印休止符的详细信息。
- duration: 休止符的时长。
返回值: 无。
self.rest_count += 1
if self.isPrintInfo:
print('第', self.rest_count, '个休止符')
print('时长(秒):', duration, '--- 时长(四分音符):', duration/self.music.quarterNote_duration)
print('\n')
#打印踏板信息
def print_pedalInfo(self):
打印踏板的详细信息。
参数: 无。
返回值: 无。
self.pedal_count += 1
if self.isPrintInfo:
print('第', self.pedal_count, '次踏板操作')
print('延音踏板(右踏板):', self.ctrl.check_pedal_state(self.ctrl, self.music.sostenutoPedal_state))
print('弱音踏板(左踏板):', self.ctrl.check_pedal_state(self.ctrl, self.music.softPedal_state))
print('\n')
def print_endingInfo(self):
打印音乐结束的信息。
参数: 无。
返回值: 无。
if self.isPrintInfo:
print('---音乐结束---')
print('音符个数:', self.note_count)
print('和弦个数:', self.chord_count)
print('休止符个数:', self.rest_count)
print('踏板操作次数:', self.pedal_count)
print('音乐时长(秒):', round(time.time()-self.start_time, 2))
class MusicConvert:
转换音乐数据
转换音符名、midi码、简谱音符
分析曲调、bpm、拍号
计算音符时长、力度
def cvt_init(self, tune='C', bpm=120, signature=[4,4], instrument="piano", default_noteDuration=1, base_dynamics=16):
初始化音乐转换器。
- tune: 曲调, 默认为 'C'。
- bpm: 每分钟的节拍数, 默认为 120。
- signature: 拍号, 由两个整数的列表表示, 默认为 [4, 4]。
- instrument: 乐器名称, 默认为 "piano"。
- default_noteDuration: 默认音符时长, 默认为 1。
- base_dynamics: 基础力度, 默认为 16。
返回值: 无。
self.rhythm_count = 0 #计数器,用于循环强弱规律
self.base_dynamics = base_dynamics #设置基础力度为16
self.default_noteDuration = default_noteDuration #设置默认音符时长
self.instrument_id, self.instrument_name = self.instruments(instrument) #将乐器名转换为midi乐器id
self.sharp = self.tune2sharp(tune) #设置音调升降半音
self.signature = signature #设置拍号
self.rhythm = self.meter2rhythm(signature[0]) #设置强弱规律
self.quarterNote_duration = self.bpm2quarterNote_duration(bpm) #设置八分音符时长
def instruments(self, instrument):
根据乐器名称或ID, 返回相应的MIDI乐器ID和名称。
instrument (str 或 int): 乐器的名称或ID。
tuple: 包含MIDI乐器ID和名称的元组。
ValueError: 乐器ID超出有效范围或找不到乐器名称。
instruments(0) -> (0, "Acoustic Grand Piano 大钢琴 声学钢琴")
instruments("Electric Guitar clean") -> (25, "Electric Guitar clean 电吉他 清音")
#定义一个列表, 用于存储乐器名称
instruments = [
"Acoustic Grand Piano 大钢琴 声学钢琴",
"Bright Acoustic Piano 亮音钢琴",
"Electric Grand Piano 大电钢琴",
"Honky-tonk Piano 酒吧钢琴",
"Rhodes Piano 柔和的电钢琴",
"Chorused Piano 加合唱效果的电钢琴",
"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 isinstance(instrument, int) or instrument.isdigit():
if isinstance(instrument, str):
instrument = int(instrument)
if instrument < 0 or instrument > 127: #如果乐器id不在范围内,则抛出异常
raise ValueError(f'Invalid instrument id: {instrument}')
#如果乐器id在范围内,则返回乐器id和乐器名称
return instrument, instruments[instrument]
#将乐器名称转换为小写, 并去除空格和标点符号
instrument = re.sub(r'[^\w\s]', '', instrument.lower().replace(" ", ""))
for i in instruments: #遍历乐器名称列表
#转换为小写, 并去除空格和标点符号
j = re.sub(r'[^\w\s]', '', i.lower().replace(" ", ""))
#如果乐器名称在列表中, 则返回对应的midi乐器id
if instrument in j:
return instruments.index(i), i
#不存在的乐器名称, 抛出异常
raise ValueError(f'Undefined instrument: {instrument}')
def simplifiedNote2midi(self, simplified_notation):
将简化的音符转换为MIDI音符。
simplified_notation (int 或 str): 简化的音符表示法。
int: 对应的MIDI音符。
ValueError: 如果给定的音符表示法无效。
simplifiedNote2midi(1) -> 60
simplifiedNote2midi('2') -> 62
simplifiedNote2midi('3#') -> 65
if isinstance(simplified_notation, int):
return simplified_notation
simplified_notation = str(simplified_notation)
notes_dict = {
'1': 60,
'2': 62,
'3': 64,
'4': 65,
'5': 67,
'6': 69,
'7': 71,
octave = 0
accidental = 0
if simplified_notation.endswith('.'):
octave -= simplified_notation.count('.')
simplified_notation = simplified_notation.rstrip('.')
elif simplified_notation.startswith('.'):
octave += simplified_notation.count('.')
simplified_notation = simplified_notation.lstrip('.')
if '#' in simplified_notation:
accidental += 1
simplified_notation = simplified_notation.replace('#', '')
elif 'b' in simplified_notation:
accidental -= 1
simplified_notation = simplified_notation.replace('b', '')
if simplified_notation not in simplified_notation:
raise ValueError('Invalid numeric notation')
return notes_dict[simplified_notation] + 12*octave + accidental
def solfegeNote2midi(self, solfege):
将唱名(solfege)转换为MIDI音符。
solfege (int 或 str): 唱名。
int: 对应的MIDI音符。
ValueError: 如果给定的唱名无效。
solfegeNote2midi(1) -> 60
solfegeNote2midi('re') -> 62
solfegeNote2midi('mi#') -> 65
if isinstance(solfege, int): #如果输入的是midi码,则直接返回
return solfege
solfege = str(solfege).lower()
solfege_dict = {
'do': 60,
're': 62,
'mi': 64,
'fa': 65,
'sol': 67,
'la': 69,
'si': 71
octave = 0
accidental = 0
if solfege[-1].isdigit():
octave = int(solfege[-1])
if '#' in solfege:
accidental = 1
solfege.replace('#', '')
elif 'b' in solfege:
accidental = -1
solfege.replace('b', '')
letter = re.sub(r'[^a-zA-Z]', '', solfege).lower()
if letter not in solfege_dict:
raise ValueError('Invalid solfege')
midi_note = solfege_dict[letter] + (octave * 12) + accidental
return midi_note
def staffNote2midi(self, note):
将谱号音符(staffNote)转换为MIDI音符。
note (str): 谱号音符。
int: 对应的MIDI音符。
staffNote2midi('C4') -> 60
staffNote2midi('D#5') -> 75
return music21.note.Note(note).pitch.midi
def midi2staffNote(self, midi):
将MIDI音符转换为五线谱音符(staffNote)。
midi (int): MIDI音符。
str: 对应的谱号音符。
midi2staffNote(60) -> 'C4'
midi2staffNote(75) -> 'D#5'
return music21.note.Note(midi).nameWithOctave
def detect_note_representation(self, note):
检测音符的表示方式。
note (int/str): 音符。
str: 音符的表示方式。可能的取值为 "MIDI"、"Staff"、"Solfege"、"Simplified" 或 "Unknown"。
detect_note_representation(60) -> "MIDI"
detect_note_representation("C4") -> "Staff"
detect_note_representation("do") -> "Solfege"
detect_note_representation("2") -> "Simplified"
detect_note_representation("invalid") -> "Unknown"
#检查音符是否为MIDI音符编号格式
if isinstance(note, int):
return "MIDI"
#检查音符是否为五线谱音符格式
staff_notation_regex = r"[A-G|a-g][#b]?\d?"
if re.match(staff_notation_regex, note):
return "Staff"
#检查音符是否为唱名格式
solfege_notation_regex = r"do#?|reb?|mi[b]?|fa[#]?|sol[#]?|lab?|si[b]?"
if re.match(solfege_notation_regex, note.lower()):
return "Solfege"
#检查音符是否为简谱格式
simplified_notation_regex = r"\.*\d[#b]?"
if re.match(simplified_notation_regex, note):
return "Simplified"
return "Unknown"
def note2midi(self, note):
将音符转换为MIDI音符, 并根据曲调调整音高。
note (int 或 str): 音符。
int: 对应的MIDI音符。
ValueError: 如果音符表示方式无效。
note2midi(60) -> 60
note2midi('C4') -> 60
note2midi('do') -> 60
note2midi('1') -> 60
note_representation = self.detect_note_representation(note) #检测音符的表示方式
if note_representation == "Staff": #五线谱音符
note = self.staffNote2midi(note)
elif note_representation == "Solfege": #唱名
note = self.solfegeNote2midi(note)
elif note_representation == "Simplified": #简谱音符
note = self.simplifiedNote2midi(note)
elif note_representation == "Unknown": #未知音符
raise ValueError(f'Unknown note representation: {note}')
return note + self.sharp
def bpm2quarterNote_duration(self, bpm):
将每分钟节拍数(BPM)转换为四分音符的时长。
bpm (float 或 int): 每分钟节拍数。
float: 四分音符的时长(以秒为单位)。
bpm2quarterNote_duration(60) -> 1.0
bpm2quarterNote_duration(120) -> 0.5
if bpm <= 0:
raise ValueError('BPM cannot be 0 or below')
return 60/bpm
def meter2rhythm(self, beats_per_measure=4):
将每小节的拍数转换为对应的节奏。
beats_per_measure (int): 每小节的拍数(默认为4)。
list: 对应的节奏列表。
meter2rhythm(2) -> [1, 0.95]
meter2rhythm(3) -> [1, 0.9, 0.9]
meter2rhythm(4) -> [1, 0.9, 0.95, 0.9]
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
def dynamics_mark2val(self, dynamics_mark):
计算力度值对应的音量。
dynamics (int 或 str): 力度值或力度标识。
int: 对应的音量。
示例: (base_dynamics = 16)
dynamics_mark2val(8) -> 8
dynamics_mark2val('fff') -> 128
dynamics_mark2val('ff') -> 112
if type(dynamics_mark) == int: #如果输入的是力度值,则直接返回
return dynamics_mark
if dynamics_mark == 'fff':
dynamics_mul = 8
elif dynamics_mark == 'ff':
dynamics_mul = 7
elif dynamics_mark == 'f':
dynamics_mul = 6
elif dynamics_mark == 'mf':
dynamics_mul = 5
elif dynamics_mark == 'mp':
dynamics_mul = 4
elif dynamics_mark == 'p':
dynamics_mul = 3
elif dynamics_mark == 'pp':
dynamics_mul = 2
elif dynamics_mark == 'ppp':
dynamics_mul = 1
else:
dynamics_mul = 8
dynamics = int((self.base_dynamics*self.rhythm[self.rhythm_count]*dynamics_mul) - 1)
self.rhythm_count += 1
if self.rhythm_count >= len(self.rhythm):
self.rhythm_count = 0
return dynamics
def dynamics_val2mark(self, dynamics_value):
计算音量对应的力度标识。
dynamics_value (int): 音量。
str: 对应的力度标识。
示例: (base_dynamics = 16)
dynamics_val2mark(128) -> 'fff'
dynamics_val2mark(50) -> 'mp'
dynamics_val2mark(12) -> 'ppp'
if isinstance(dynamics_value, str):
return dynamics_value
dynamics_value /= self.base_dynamics
if dynamics_value >= 0 and dynamics_value < 1:
return 'ppp'
if dynamics_value >= 1 and dynamics_value < 2:
return 'pp'
if dynamics_value >= 2 and dynamics_value < 3:
return 'p'
if dynamics_value >= 3 and dynamics_value < 4:
return 'mp'
if dynamics_value >= 4 and dynamics_value < 5:
return 'mf'
if dynamics_value >= 5 and dynamics_value < 6:
return 'f'
if dynamics_value >= 6 and dynamics_value < 7:
return 'ff'
if dynamics_value >= 7 and dynamics_value <= 8:
return 'fff'
raise ValueError('Value out of range')
def count_duration(self, duration):
计算音符的持续时长。
duration (float 或 None): 音符的持续时长(以四分音符为单位)。
float: 音符的实际持续时长(以秒为单位)。
count_duration(1) -> 1.0
count_duration(0.5) -> 0.5
count_duration(None) -> 1.0
if duration is None:
duration = self.default_noteDuration
return self.quarterNote_duration * duration
def tune2sharp(self, tune='C'):
调整音符的音高。
tune (str): 调音方式(默认为'C')。可能的取值为 'C'、'C#'、'Db'、'D'、'Eb'、'E'、'F'、'F#'、'Gb'、'G'、'Ab'、'A'、'Bb'、'B' 和 'Cb'。
ValueError: 调音方式无效。
adjust_semitone(60, 'C') -> 60
adjust_semitone(60, 'D') -> 62
adjust_semitone(60, 'Bb') -> 59
if isinstance(tune, int):
return tune
if isinstance(tune, str):
tune = tune[0].upper() + tune[1:].lower()
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 tunes_dict[tune]
class MidiControl(Check, PrintMusicInfo, MusicConvert):
MidiControl类用于控制MIDI设备。
output (MIDI输出设备): MIDI输出设备。
prt (PrintMusicInfo类的对象): PrintMusicInfo类的对象。
chk (Check类的对象): Check类的对象。
vibrato_state (bool): 是否启用颤音效果的状态。
pitch_state (bool): 是否启用滑音效果的状态。
sostenutoPedal_state (bool): 是否启用延音踏板(右踏板)的状态。
damperPedal_state (bool): 是否启用弱音踏板(中踏板)的状态。
softPedal_state (bool): 是否启用柔音踏板(左踏板)的状态。
def ctr_init(self, output):
初始化MidiControl对象。
output (MIDI输出设备): MIDI输出设备。
self.output = output #midi输出设备
self.vibrato_state = False #是否启用颤音效果
self.pitch_state = False #是否启用滑音效果
self.sostenutoPedal_state = False #是否启用延音踏板(右踏板)
self.softPedal_state = False #是否启用弱音踏板(左踏板)
def send(self, status, data1=0, data2=0):
发送MIDI消息。
status (int): MIDI消息的状态字节。
data1 (int): MIDI消息的数据字节1(默认为0)。
data2 (int): MIDI消息的数据字节2(默认为0)。
self.output.write_short(status, data1, data2)
def vibrato(self, vibrato_depth=None, vibrato_speed=None, on_off=None):
设置颤音效果。
vibrato_depth (int): 颤音深度(默认为None)。
vibrato_speed (int): 颤音速度(默认为None)。
on_off (bool): 是否启用颤音效果(默认为None)。
self.vibrato_state = self.check_on_off(on_off, self.vibrato_state) #设置颤音效果状态
if vibrato_depth is not None and vibrato_speed is not None: #如果传入了颤音深度和颤音速度
if self.vibrato_state: #如果启用颤音效果
self.send(0xB0, 1, 1) #启用Modulation控制器(控制器号1)
self.send(0xB0, 1, vibrato_depth) #设置颤音深度
self.send(0xB0, 1, vibrato_speed) #设置颤音速度
else: #如果禁用颤音效果
self.send(0xB0, 1, 0) #禁用Modulation控制器
def pitch(self, pitch_bend=None, on_off=None):
设置滑音效果。
pitch_bend (int): 滑音值(默认为None)。
on_off (bool): 是否启用滑音效果(默认为None)。
self.pitch_state = self.check_on_off(on_off, self.pitch_state) #设置滑音效果状态
if pitch_bend is not None: #如果传入了滑音值
if self.pitch_state: #如果启用滑音效果
self.send(0xE0, pitch_bend & 0x7F, (pitch_bend >> 7) & 0x7F) #发送滑音消息
else: #如果禁用滑音效果
self.send(0xE0, 0, 0) #发送滑音结束消息
def sosPedal(self, on_off=None):
踩下或松开延音踏板(右踏板)。
value (int): 踏板的值(默认为127)。
on_off (bool): 是否踩下延音踏板(默认为None)。
self.sostenutoPedal_state = self.check_on_off(on_off, self.sostenutoPedal_state) #设置延音踏板状态
if self.sostenutoPedal_state: #如果延音踏板踩下
self.quarterNote_duration *= 4 #调整音符持续时间
else: #如果延音踏板松开
self.quarterNote_duration /= 4
self.print_pedalInfo() #打印踏板信息
def softPedal(self, on_off=None, value=63):
踩下或松开柔音踏板(左踏板)。
value (int): 延音值,默认为63。
on_off (bool): 是否踩下柔音踏板,不输入时默认翻转踏板状态。
self.softPedal_state = self.check_on_off(on_off, self.softPedal_state) #设置柔音踏板状态
if self.softPedal_state: #如果柔音踏板踩下
self.base_dynamics /= 1.5 #调整音符基础力度
else: #如果柔音踏板松开
self.base_dynamics *= 1.5
self.print_pedalInfo() #打印踏板信息
class Music(MidiControl):
这个类提供一个使用`pygame.midi`模块的MIDI音乐播放器。
MusicConvert: 提供将音乐符号转换为MIDI格式的方法。
PrintMusicInfo: 提供打印音乐信息的方法。
MidiControl: 提供控制MIDI设备和效果的方法。
bpm(int): 每分钟的节拍数。默认为120。
tune(str): 音乐的曲调。默认为'C'。
signature(list[int]): 音乐的拍号, 形式为[分子, 分母]。默认为[4, 4]。
default_noteDuration(int): 音符的默认持续时间, 以四分音符为单位。默认为1。
instrument(str): 要使用的乐器。可以使用名称或编号指定。默认为'钢琴'。
base_dynamics(int): 音乐的基本力度。默认为16。
isPrintInfo(bool): 是否打印音乐信息。默认为True。
device_id(int): 要使用的MIDI设备的ID。默认为0。
channel(int): 要使用的MIDI通道。默认为0。
__init__(): 初始化方法。
change_args(): 改变MidiMusic类的属性。
play(): 播放单个音符。
playChord(): 播放和弦。
rest(): 播放休止符。
close(): 停止播放。
有关更多方法及其描述, 请参阅`MusicConvert`, `PrintMusicInfo`和`MidiControl`类。
**注意**:
-在多线程中播放音乐时, 打印输出可能会混乱。
-不可创建多个此类的实例。
def __init__(self, bpm=120, tune='C', signature=[4,4], default_noteDuration=1, instrument="piano", isPrintInfo=True, base_dynamics=16, device_id=0, channel=0):
初始化方法, 用于设置MidiMusic类的属性和初始化相关对象。
bpm (int): 每分钟的节拍数。默认为120。
tune (str): 音乐的曲调。默认为'C'。
signature (list[int]): 音乐的拍号, 形式为[分子, 分母]。默认为[4, 4]。
default_noteDuration (int): 音符的默认持续时间, 以四分音符为单位。默认为1。
instrument (str/int): 要使用的乐器。可以使用名称或编号指定。默认为'钢琴'。
base_dynamics (int): 音乐的基本力度。默认为16。
isPrintInfo (bool): 是否打印音乐信息。默认为True。
device_id (int): 要使用的MIDI设备的ID。默认为0。
channel (int): 要使用的MIDI通道。默认为0。
pygame.midi.init() #初始化pygame.midi
self.output = Output(device_id) #设置输出设备, 默认设备id为0
self.cvt_init(tune, bpm, signature, instrument, default_noteDuration, base_dynamics) #初始化音乐转换
self.output.set_instrument(self.instrument_id, channel) #设置乐器, 0~127
self.prt_init(self, channel, tune, isPrintInfo) #初始化打印音乐信息
self.ctr_init(self.output)
self.print_musicInfo() #打印音乐信息
def change_args(self, bpm=120, tune='C', signature=[4,4], default_noteDuration=1, instrument="piano", isPrintInfo=True, base_dynamics=16, channel=0):
改变MidiMusic类的属性。
bpm (int): 每分钟的节拍数。默认为120。
tune (str): 音乐的曲调。默认为'C'。
signature (list[int]): 音乐的拍号, 形式为[分子, 分母]。默认为[4, 4]。
default_noteDuration (int): 音符的默认持续时间, 以四分音符为单位。默认为1。
instrument (str/int): 要使用的乐器。可以使用名称或编号指定。默认为'钢琴'。
base_dynamics (int): 音乐的基本力度。默认为16。
isPrintInfo (bool): 是否打印音乐信息。默认为True。
channel (int): 要使用的MIDI通道。默认为0。
**注意**:经测试, 无法改变设备id。
self.cvt_init(tune, bpm, signature, instrument, default_noteDuration, base_dynamics) #初始化音乐转换
self.prt_init(self, channel, tune, isPrintInfo) #初始化打印音乐信息
self.ctr_init(self.output)
self.output.set_instrument(self.instrument_id, channel) #设置乐器, 0~127
self.print_musicInfo() #打印音乐信息
def play(self, note='C', duration=None, legato=False, dynamics='fff', v_depth=None, v_speed=None, p_bend=None, newThread=False, isPrintInfo=True):
播放音符的方法。
note (str): 要播放的音符。默认为'C'。
duration (float): 音符的持续时间。默认为None。
legato (bool): 是否使用连音效果。默认为False。
dynamics (str): 音符的力度。默认为'fff'。
v_depth (int): 颤音的深度。默认为None。
v_speed (int): 颤音的速度。默认为None。
p_bend (int): 滑音的弯曲程度。默认为None。
newThread (bool): 是否在新线程中播放音符。默认为False。
isPrintInfo (bool): 是否打印音符信息。默认为True。
if newThread: #如果新建线程播放
Thread(target=self.play, args=(note, duration, legato, False, dynamics, v_depth, v_speed, p_bend, False)).start() #新建线程播放
else: #如果不新建线程播放
self.vibrato(v_depth, v_speed, 'on') #设置颤音效果
self.pitch(p_bend, 'on') #设置滑音效果
dynamics_value = self.dynamics_mark2val(dynamics) #计算力度
duration = self.count_duration(duration) #计算时长
if isPrintInfo:
self.print_noteInfo(note, duration, dynamics_value) #打印音符信息
note = self.note2midi(note) #将音符名转换为midi码, 并根据曲调调整音高
self.output.note_on(note, dynamics_value) #播放音符
if not legato:
time.sleep(duration) #等待一定时长
self.output.note_off(note, dynamics_value) #停止播放音符
self.pitch(p_bend, 'off') #禁用滑音效果
self.vibrato(v_depth, v_speed, 'off') #禁用颤音效果
def playChord(self, *notes, duration=None, legato=False, dynamics='fff', v_depth=None, v_speed=None, p_bend=None, isPrintInfo=True):
播放和弦的方法。
*notes (str): 要播放的音符。可以接受多个音符作为参数。
duration (float): 音符的持续时间。默认为None
legato (bool): 是否使用连音效果。默认为False。
dynamics (str): 音符的力度。默认为'fff'。
if len(notes) == 1: #如果只有一个音符
self.play(notes[0], duration, legato, False, dynamics) #播放单个音符
else: #如果有多个音符
duration = self.count_duration(duration) #计算时长
dynamics_value = self.dynamics_mark2val(dynamics) #计算力度
self.print_chordInfo(duration, dynamics_value, *notes)#打印和弦信息
for note in notes:
Thread(target=self.play(note, duration, False, dynamics_value, v_depth, v_speed, p_bend, False)).start() #播放单个音符
if not legato:
time.sleep(duration) #等待一定时长
def rest(self, duration=None):
播放休止符的方法。
duration (float): 休止符的持续时间。默认为None。
duration = self.count_duration(duration) #计算时长
self.print_restInfo(duration) #打印休止符信息
time.sleep(duration) #等待一定时长
def close(self):
关闭方法。
用于关闭 MIDI 输出和停止 MIDI 模块。
self.print_endingInfo() #打印结束信息
self.output.close()
pygame.midi.quit()
#---调试程序用---#
m=Music()
''' pg_
midi
_sound101.py
play
midi
music files (also mp3 files) using
pygame
tested with
Python
273/331 and
pygame
192 by vegaseat
import
pygame
as pg
def play_music(music_file):
stream music with mixer.music module in blocking mann
pygame
.
midi
用于与
midi
输入和输出交互的
pygame
模块
。
pygame
.
midi
.Input - 输入用于从
midi
设备获取
midi
输入。
pygame
.
midi
.
Midi
Exception -
pygame
.
midi
函数和类可以引发的异常
pygame
.
midi
.Output - 输出用于将
midi
发送到输出设备
pygame
.
midi
.get_count - 获取设备...
@【
pygame
】
pygame
的init()
【转】https://www.cnblogs.com/scott-lv/p/9280154.html
当我们在init()的时候,我们在干什么
init 这个单词在我们用
python
进行面向对象开发的时候是跑不了的。理解
python
的__init__其实就是和这里的init作用差不多。做的工作都是__初始化__.
在和孩子解释这个概念的时候,我的理解还...
# set enemy tank starting position
enemy_x = random.randint(0, 800)
enemy_y = random.randint(50, 150)
# load enemy tank image
enemy_img =
pygame
.image.load("enemy.png")
# set bullet starting position
bullet_x = 0
bullet_y = 0
# load bullet image
bullet_img =
pygame
.image.load("bullet.png")
# game loop
running = True
while running:
for event in
pygame
.event.get():
if event.type ==
pygame
.QUIT:
running = False
# move tank
keys =
pygame
.key.get_pressed()
if keys[
pygame
.K_LEFT]:
tank_x -= 5
if keys[
pygame
.K_RIGHT]:
tank_x += 5
if keys[
pygame
.K_UP]:
tank_y -= 5
if keys[
pygame
.K_DOWN]:
tank_y += 5
# fire bullet
if keys[
pygame
.K_SPACE]:
bullet_x = tank_x + 20
bullet_y = tank_y - 20
# move bullet
bullet_y -= 5
# check if bullet hit enemy
if bullet_x >= enemy_x and bullet_x <= enemy_x + 40 and bullet_y >= enemy_y and bullet_y <= enemy_y + 40:
enemy_x = random.randint(0, 800)
enemy_y = random.randint(50, 150)
bullet_x = 0
bullet_y = 0
# redraw screen
screen.fill((0, 0, 0))
screen.blit(tank_img, (tank_x, tank_y))
screen.blit(enemy_img, (enemy_x, enemy_y))
screen.blit(bullet_img, (bullet_x, bullet_y))
pygame
.display.update()
# quit game
pygame
.quit()
这个代码实现了一个简单的坦克大战游戏,其中包括操纵坦克移动和开火、敌方坦克随机生成和碰撞检测等功能
CSDN-Ada助手:
Python简易贪吃蛇游戏
CSDN-Ada助手: