Как-то заинтересовался я теорией музыки. Но пианино, увы, у меня нет, поэтому я отправился в поиски программы со следующий функционалом: после нажатия на кнопку звучит определенная нота. Сперва я посмотрел высокопрофессиональные программы, но в них слишком, уж слишком много функций. И это очень хорошо, но на текущем моменте моей жизни мне это попросту ненужно. Это будет только мешать и отвлекать. В программах с меньшим функционалом максимально неудобный интерфейс. Потому я решил просто написать такую программу сам. Подробности под катом.
Немножко о 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
Спасибо за прочтение статьи. Удачи!
Catslinger
Уж если играть на компьютерной клавиатуре, то на ней не надо имитировать рояль. Не годится. Есть более подходящие раскладки, где нет пустых клавиш, зато работает правило "понял принцип = выучил все аккорды".
unsignedchar
На обычной клавиатуре можно играть (а на midi- контроллере набирать текст). Но зачем?
Catslinger
Затем, что MIDI контроллер начинается от 6000 руб. Можно найти за 4, но там ещё вопрос, лучше ли это, чем клавиатура. 6000, напомню, это треть от официальной зарплаты четверти россиян. Да, а когда я искал MIDI контроллер именно в Гайдновской раскладке — самый дешёвый был 34000 + под заказ 3 месяца.
lab412
мне кажется у тех, чтя зарплата в треть от цены MIDI клавы — не имеют компьютера вообще… чаще всего у них и интересы иные, но это уже другая история…
AlexBrown
Сильно ошибаетесь.
unsignedchar
Ну ок, имеют. Но, видимо, без видеокарты.
stephanthe
Ну мой доход позволяет взять миди клавиатуру, но в компьютере тоже нет видеокарты. Это как-то может помешать музыке?
unsignedchar
То есть у вас нет ни видеокарты, ни midi-клавиатуры ;) Значит, ни одно ни другое вам не нужно.
Музыке ничто не сможет помешать ;) Издавать музыкальные звуки можно из чего угодно, но профессиональные музыканты обычно играют на дорогих музыкальных инструментах.
lab412
ничто не остановит человека от творческой самореализации. можете петь в душе — там акустика хорошая и без всяких примочек получаете звучное эхо. еще можно свистеть, это целое искусство и не требует лишних трат
AlexBrown
Да, они битки не майнят.
Daiwery Автор
Ну вот я обычный студент. У меня дешевенький комп, а денег свободных нет вообще. Позволить купить себе MIDI-клавиатуру я не могу.
Camel
Ардуина (насколько я понял не всякая, но promicro вроде да) может работать USB-MIDI конвертором. Я планирую собрать клавиатуру по классической схеме эргосплитов, но у меня будут подключаться проводом обе половинки. Правая будет USB HID, то есть давать буквы, а левая — MIDI, будет через fluidsynth или что-то подобное пиликать разными звуками. (Ну и между половинками свясь по проводу, само собой, если вдруг вы не в теме самодельных эргосплитов).
werevolff
Всё правильно, парень! Пили код и ни в чём себе не отказывай.
eternal_why
Ну вот у меня за спиной стоит MPK-88, подключенная к другому компу, а на рабочем столе этого, рабочего, всё равно висят ярлыки на VMPK и Vanilin MIDI Keyboard. Ну вот хочется иногда и пошалить, а иногда и просто проверить себя по-быстрому. А вот двадцать лет назад, тыкая мышой в Cakewalk'е — как я был рад какой-то такой же поделке. Дела резко в гору пошли! :) Правда, ненадолго, но это уже другой вопрос.
Автору респект и успехов!
Camel
Я планирую на клавиатуре набирать текст и пиликать звуками. Скорее всего ерунда получится, но только эксперимент может это подтвердить или опровергнуть. Более подробное объяснение в моём ответе пользователю keydon2.
werevolff
Затем, что ты программист. Допустим, тебе просто интересно это реализовать, но покупать специализированную клавиатуру не нужно. Или у заказчика есть такая клавиатура, и он поручает тебе внести правку в приложение. А правка — на пару часов.