
Как-то заинтересовался я теорией музыки. Но пианино, увы, у меня нет, поэтому я отправился в поиски программы со следующий функционалом: после нажатия на кнопку звучит определенная нота. Сперва я посмотрел высокопрофессиональные программы, но в них слишком, уж слишком много функций. И это очень хорошо, но на текущем моменте моей жизни мне это попросту ненужно. Это будет только мешать и отвлекать. В программах с меньшим функционалом максимально неудобный интерфейс. Потому я решил просто написать такую программу сам. Подробности под катом.
Немножко о MIDI
MIDI - стандарт цифровой звукозаписи на формат обмена данными между электронными музыкальными инструментами. Это отдельный большой мир, который заслуживает отдельного разговора. Но нам необходимо знать лишь некоторые правила:
В каждом файле midi есть неограниченное количество треков, которые запускаются одновременно.
В каждом треке хранятся определенные команды для синтезатора. Например, noteon – включить определенную ноту; noteoff – выключить определенную ноту; change_program – изменить инструмент, control_change – изменение настроек, влияющих на воспроизведение нот, их смену, и тп. Все команды можно посмотреть здесь.
Каждая команда характеризуется несколькими параметрами: значение – это, например, номер ноты, номер инструмента и т.п; время от прошлой команды, через которое необходимо выполнить эту команду; номер канала (всего их 16), в котором играет данная нота или применяется соответствующая настройка, или изменяется инструмент. Если не включена полифония, то в канале не может звучать две одинаковые ноты одновременно.
Запись MIDI-файлов с помощью Mido
Mido – это библиотека на python, созданная для работы с MIDI-сообщениями и портами. Установка.
Классический пример прочтения файла:
from mido import MidiFile mid = MidiFile('song.mid') for i, track in enumerate(mid.tracks): print('Track {}: {}'.format(i, track.name)) for msg in track: print(msg)
Классический пример создания файла:
from mido import Message, MidiFile, MidiTrack, second2tick mid = MidiFile() track = MidiTrack() mid.tracks.append(track) time = int(second2tick(0.1, 480, 500000)) for i in range(100): track.append(Message('program_change', program=12, time=0)) track.append(Message('note_on', note=64, velocity=64, time=time)) track.append(Message('note_off', note=64, velocity=64, time=time)) mid.save('new_song.mid')
Обратите внимание на параметр «time». Поподробней можно прочитать здесь.
Обработка событий клавиатуры c keyboard.
Классический пример:
import keyboard def hook(key): if key.event_type == "down": print("{} press".format(key.name)) if key.event_type == "up": print("{} release".format(key.name)) keyboard.hook(hook) keyboard.wait("esc")
Словарь {key: note} можно сделать так (начинается с малой октавы):
import keyboard keys = {} note = 48 def hook(key): global note if key.event_type == "down": if key.name != "esc": keys.update({key.name: note}) note += 1 if key.event_type == "up": if key.name == "esc": print(keys) keyboard.hook(hook) keyboard.wait()
Воспроизведениe нот в реальном времени с помощью mido
Сперва надо установить python-rtmidi.
Получаем список портов (у меня всего один):
>>> mido.get_output_names() ['Microsoft GS Wavetable Synth 0']
При нажатии на клавишу передаем порту сообщение о включении или выключении ноты:
import keyboard import mido port = mido.open_output('Microsoft GS Wavetable Synth 0') keys = keys = {'1': 48, '2': 49, '3': 50, '4': 51, '5': 52, '6': 53, '7': 54, '8': 55, '9': 56, '0': 57, '-': 58, '=': 59, 'q': 60, 'w': 61, 'e': 62, 'r': 63, 't': 64, 'y': 65, 'u': 66, 'i': 67, 'o': 68, 'p': 69, '[': 70, ']': 71, 'a': 72, 's': 73, 'd': 74, 'f': 75, 'g': 76, 'h': 77, 'j': 78, 'k': 79, 'l': 80, ';': 81, "'": 82, 'enter': 83} pressed_keys = {key: False for key in keys.keys()} def hook(key): if key.event_type == "down": if key.name in keys: if not pressed_keys[key.name]: port.send(mido.Message('note_on', note=keys[key.name])) pressed_keys[key.name] = True if key.event_type == "up": if key.name in keys: port.send(mido.Message('note_off', note=keys[key.name])) pressed_keys[key.name] = False keyboard.hook(hook) keyboard.wait()
Но у этого способа есть одна проблема – качество звучания. Да и превратить midi в wav просто так нельзя.
Воспроизведениe нот в реальном времени с помощью fluidsynth
Fluidsynth – это бесплатный программный синтезатор.
Установка fluidsynth (в Windows):
Скачайте fluidsynth для Windows и распакуйте в любой папке.
Добавьте подкаталог «fluidsynth\bin» в свой path. Для этого в поисковой строке напишете «Изменение системных переменных среды», запустите; далее по порядку «Переменные среды», «Path», «Изменить», «Создать» и введите путь к подкаталогу «fluidsynth\bin».
Теперь нужно проверить работоспособность fluidsynth. Скачайте любой midi файл и выполните в консоли «fluidsynth FluidR3_GM.sf2 file_name.mid». Не забудьте перейти в необходимый каталог.
Теперь нужно установить pyfluidsynth.
Скачайте pyfluidsynth (разработка ведется на github) и распакуйте.
Чтобы додуматься до этого шага мне пришлось потратить 1.5 дня (еще один намек на то, чтобы нормально выучить язык, а не с помощью статей в интернете). Перейдите в каталог «fluidsynth\bin» и найдите там файл «libfluidsynth-3.dll» (Быть может, у вас другая цифра). Теперь откройте файл «fluidsynth.py» в каталоге «pyfluidsynth», найдите строчку «lib = find_library('fluidsynth') or…» (она должна быть в начале) и поменяйте «fluidsynth» или любой другой аргумент на «libfluidsynth-3.dll» (У вас может быть другая цифра).
В каталоге «pyfluidsynth» выполните команду «py setup.py install». После чего данный каталог можно удалить.
Также может потребоваться установить numpy.
Классический пример:
import time import fluidsynth fs = fluidsynth.Synth() fs.start() sfid = fs.sfload("FluidR3_GM.sf2") fs.program_select(0, sfid, 0, 0) for i in range(10): fs.noteon(0, 60, 30) fs.noteon(0, 67, 30) fs.noteon(0, 76, 30) time.sleep(1.0) fs.noteoff(0, 60) fs.noteoff(0, 67) fs.noteoff(0, 76) time.sleep(1.0) fs.delete()
Соединяем с keyboard:
import keyboard import mido import fluidsynth fs = fluidsynth.Synth() fs.start() sfid = fs.sfload("FluidR3_GM.sf2") fs.program_select(0, sfid, 0, 41) keys = {'1': 48, '2': 49, '3': 50, '4': 51, '5': 52, '6': 53, '7': 54, '8': 55, '9': 56, '0': 57, '-': 58, '=': 59, 'q': 60, 'w': 61, 'e': 62, 'r': 63, 't': 64, 'y': 65, 'u': 66, 'i': 67, 'o': 68, 'p': 69, '[': 70, ']': 71, 'a': 72, 's': 73, 'd': 74, 'f': 75, 'g': 76, 'h': 77, 'j': 78, 'k': 79, 'l': 80, ';': 81, "'": 82, 'enter': 83} pressed_keys = {key: False for key in keys.keys()} def hook(key): if key.event_type == "down": if key.name in keys: if not pressed_keys[key.name]: fs.noteon(0, keys[key.name], 127) pressed_keys[key.name] = True if key.event_type == "up": if key.name in keys: fs.noteoff(0, keys[key.name]) pressed_keys[key.name] = False keyboard.hook(hook) keyboard.wait()
Из midi в wav
Выполните в консоли:
fluidsynth -F melody.wav FluidR3_GM.sf2 melody.mid
Спасибо за прочтение статьи. Удачи!
