9 июня в 23:35

Питон, смещение тона и Пианопьютер перевод

От переводчика:

Статья, которую я предлагаю вам почитать, не нова — она опубликована аж 29 марта. Но на Реддите ее запостили всего несколько дней назад, да и актуальности своей она точно не потеряла. Интересность ее в том, что автор на простом и коротком примере демонстрирует практическое применение трех больших и популярных библиотек: numpy, scipy и pygame. Про первые две многие слышали, но все больше в контексте научных работ, так что интересно посмотреть на их применение в «обычной» жизни. В конце статьи прекрасная видео-демонстрация результата, хотя бы ее точно стоит посмотреть.

Авторский код сохранен без изменений, несмотря на то, что он оформлен не по PEP-8 и за его валидность я не ручаюсь. Настоящий рабочий код так или иначе есть на ГитХабе, ссылку вы найдете в конце статьи.

Запишите звук, измените тон 50 раз и сопоставьте каждому новому звуку клавишу на клавиатуре компьютера. Получится Пианопьютер!



Звук можно закодировать как массив (или список, list) значений, примерно вот так:



Чтобы проиграть этот звук вдвое быстрее, удалим каждое второе значение в массиве:



Сделав это, мы не только сократили вдвое длительность звука, но и удвоили его частоту, поэтому и его тон стал выше.

Напротив, если повторить каждое значение, то получится более медленный звук, с более длинным периодом, а значит, и ниже тоном:



Вот простая функция на Питоне, которая меняет скорость звука соответственно переданному коэффициенту:

import numpy as np

def speedx(sound_array, factor):
    """ Multiplies the sound's speed by some `factor` """
    indices = np.round( np.arange(0, len(snd_array), factor) )
    indices = indices[indices < len(snd_array)].astype(int)
    return sound_array[ indices.astype(int) ]


Cложнее изменить длительность, сохранив при этом тон (растягивание звука), или изменить тон, сохранив длительность (смещение тона).

Растягивание звука



Растянуть звук можно, используя классический метод фазового вокодера (phase vocoder). Сначала разбиваем звук на пересекающиеся куски, а затем перемещаем их так, чтобы они пересекались больше (чтобы сократить звук) или меньше (чтобы его растянуть), как на картинке:



Сложность здесь заключается в том, что передвинутые куски могут плохо взаимодействовать, и необходима определенная фазовая трансформация, чтобы этого не произошло. Вот код на Питоне, вольно переписанный отсюда:

def stretch(sound_array, f, window_size, h):
    """ Stretches the sound by a factor `f` """

    phase  = np.zeros(window_size)
    hanning_window = np.hanning(window_size)
    result = np.zeros( len(sound_array) /f + window_size)

    for i in np.arange(0, len(sound_array)-(window_size+h), h*f):

        # two potentially overlapping subarrays
        a1 = sound_array[i: i + window_size]
        a2 = sound_array[i + h: i + window_size + h]

        # resynchronize the second array on the first
        s1 =  np.fft.fft(hanning_window * a1)
        s2 =  np.fft.fft(hanning_window * a2)
        phase = (phase + np.angle(s2/s1)) % 2*np.pi
        a2_rephased = np.fft.ifft(np.abs(s2)*np.exp(1j*phase))

        # add to result
        i2 = int(i/f)
        result[i2 : i2 + window_size] += hanning_window*a2_rephased

    result = ((2**(16-4)) * result/result.max()) # normalize (16bit)

    return result.astype('int16')


Смещение тона



После растягивания звука смещение тона делается просто. Чтобы получить более высокий тон, растягиваем звук, сохраняя тон, а затем ускоряем результат, чтобы финальный звук имел ту же длину, что и изначальный, но более высокий тон из-за изменения скорости.

Удвоение частоты звука повысит тон на одну октаву, или 12 полутонов. Таким образом, чтобы повысить тона на n полутонов, надо умножить высоту на 2^(n/12):

def pitchshift(snd_array, n, window_size=2**13, h=2**11):
    """ Changes the pitch of a sound by ``n`` semitones. """
    factor = 2**(1.0 * n / 12.0)
    stretched = stretch(snd_array, 1.0/factor, window_size, h)
    return speedx(stretched[window_size:], factor)


Приложение: Пианопьютер



Давайте испытаем наш новый тоносместитель. Для начала стукнем по чаше:



Затем создадим 50 производных звуков от очень низкого до очень высокого:

from scipy.io import wavfile

fps, bowl_sound = wavfile.read("bowl.wav")
tones = range(-25,25)
transposed = [pitchshift(bowl_sound, n) for n in tones]


Каждой клавише на клавиатуре назначим звук, следуя порядку, заданному в этом файле, вот так:



А вот код на Питоне, который превращает ваш компьютер в пианино (пианопьютер):

import pygame

pygame.mixer.init(fps, -16, 1, 512) # so flexible ;)
screen = pygame.display.set_mode((640,480)) # for the focus

# Get a list of the order of the keys of the keyboard in right order.
# ``keys`` is like ['Q','W','E','R' ...] 
keys = open('typewriter.kb').read().split('\n')

sounds = map(pygame.sndarray.make_sound, transposed)
key_sound = dict( zip(keys, sounds) )
is_playing = {k: False for k in keys}

while True:

    event =  pygame.event.wait()

    if event.type in (pygame.KEYDOWN, pygame.KEYUP):
        key = pygame.key.name(event.key)

    if event.type == pygame.KEYDOWN:

        if (key in key_sound.keys()) and (not is_playing[key]):
            key_sound[key].play(fade_ms=50)
            is_playing[key] = True

        elif event.key == pygame.K_ESCAPE:
            pygame.quit()
            raise KeyboardInterrupt

    elif event.type == pygame.KEYUP and key in key_sound.keys():

        key_sound[key].fadeout(50) # stops with 50ms fadeout
        is_playing[key] = False


Вот и все! А теперь я сыграю вам традиционную турецкую песенку (на самом деле нет. Прим. перев.)!



Если хотите попробовать то же самое дома, вот все файлы, которые вам понадобятся. Думаю, было бы здорово, если бы кто-то среди читателей из HTML5/JS/elm-разработчиков создал браузерную версию Пианопьютера, так он стал бы доступен более широкой аудитории.

Если говорить вообще, мне кажется, что компьютеры недостаточно используют именно для исполнения музыки. Я понимаю, что легче взять настоящую фортепианную клавиатуру или записать настоящий инструмент, но вы только посмотрите, чего можно добиться с помощью обычной чаши и 60 строк на Питоне!

Даже дешевый компьютер имеет достаточно элементов управления, чтобы стать полноценной музыкальной станцией: можно петь в микрофон, показывать жесты через веб-камеру, модулировать всякие штуки мышкой и управлять остальным с клавиатуры. Столько средств самовыражения, и для каждого есть библиотека на Питоне… Артистичные хакеры, никто не желает шагнуть в эту сторону?
+31
9978
90
moigagoo 9,1

Комментарии (17)

+4
Zlobober, #
Я когда развлекался с подобными вещами постоянно натыкался на проблему, что клавиатуры моих ноутбуков не держали нормально больше двух-трёх кнопок нажатыми. В турецком марше это ещё некритично, а вот в каких-нибудь других произведениях уже напрягает.
И да, круто!
+5
degorov, #
А ещё у компьютерной клавиатуры нет возможности воспринимать силу/резкость нажатия, что не менее важно.
0
Michael134096, #
Один из вариантов имитации пианино на компьютере. www.virtualpiano.net/
(Плюсы — браузер, хороший интерфейс. Главный минус проекта — нельзя нажимать одновременно белые и черные клавиши — они переключаются через шифт). Некоторые видео на виртуалпиано тем не менее все равно завораживают.
0
philpirj, #
Эта проблема появилась со времён USB, и кроме варианта эмуляции нескольких устройств в одном (клавиатуры Happy hacking) теперь вариантов нет. В ноутбуках проблема, скорее всего, вызвана тем, что производитель сэкономил на диодах, которые можно поставить на каждую клавишу, чтобы избежать этой проблемы.
0
n1tra, #
Не совсем ясна идея с диодами. Не могли бы раскрыть мысль?
0
merlin-vrn, #
Клавиатура устроена так: есть сетка m x n проводов, пересечения — клавиши-выключатели, которые замыкают вертикальный i-й провод с горизонтальным j-м.

Как происходит опрос? Контроллер постоянно по очереди посылает на вертикальные провода 1 по кругу, и слушает, на каком из горизонтальных появился или пропал сигнал по сравнению с прошлым кругом, то есть нажали или отпустили клавишу.
Что будет если нажать несколько клавиш одновременно? Если они не находятся в одной колонке, то ничего страшного, все определятся. А если лежат? Может быть такая ситуация, что зажали (1,1), (1,2) и (2,1). Можете проверить, любой алгоритм опроса решит, что ещё дополнительно зажата (2,2), хотя на самом деле её нет среди нажатых.

А если клавиши сделать диодами (т.е. при нажатии не просто замыкается горизотнальный и вертикальный проводники, но соединяются через диод), то такое ложное замыкание зарегистрировано не будет. Но это дороже.
0
n1tra, #
Принцип понятен, спасибо.
Т.е. пленочные клавиатуры автоматом идут в топку.
Есть механические клавиатуры с кнопками устроенными таким образом?
И что с ограничением ОС, т.е. слышал что более 10(или около того) одновременно нажатых клавиш в принципе не поддерживается.
0
merlin-vrn, #
Я сказал бы, что нет компьютерных клавиатур, устроенных по-другому :) Плёночные или механические различаются устройством выключателей на клавишах, а не их коммутацией.

Ограничение на число клавиш в ОС вытекает совсем из другой оперы, а именно из свойств контроллера клавиатуры AT — чипа i8042 или его современного воплощения, а для USB, емнип 6, не помню, откуда берётся. Есть устройства, на которых нет такого ограничения — например, MIDI-клавиатуры, они никак не связаны с указанным интерфейсом (они, правда, устроены сильно по-другому).
0
n1tra, #
Про пленочные — я имел в виду что на пленке не очень-то удобно ставить диоды в принципе :)
Спасибо что разъяснили.
0
Steve_Key, #
А на «плёночных» клавах диоды и не ставят, ПОЭТОМУ, в частности, у них и ограничения в кол-ве одновременно нажатых кнопок…
–3
wiygn, #
Окей, а теперь раскидайте ноты по клавишам так, чтобы на выходе получить код quicksort.
+7
merlin-vrn, #
Чтобы проиграть этот звук вдвое быстрее, удалим каждое второе значение в массиве:

Сделав это, мы не только сократили вдвое длительность звука, но и удвоили его частоту, поэтому и его тон стал выше.

А ещё мы добавили искажения. Не успокаивайте себя, искажения вполне слышимые на слух.

Удивительно? Про aliasing не слышали? Перед децимацией нужно выполнять фильтрацию высоких частот (подавлять те, которые окажутся после децимации выше частоты Найквиста).

Напротив, если повторить каждое значение, то получится более медленный звук, с более длинным периодом, а значит, и ниже тоном:

И мерзким свистом впридачу. А можно было вообще-то и не повторять значения, а вставить нули — это роли не играет и на результате не сказывается. На этот раз low-pass фильтрация делается после разрежения сэмплов.

Я хочу подчекрнуть, что это не какие-то там заоблачные вопросы, это самые основы цифрового звука. Всё это прямо следует из обоснования идеи, то есть, из теоремы Котельникова. Их игнорировать нельзя даже в самом примитивном варианте. Особенно, если вы сразу после этого начинаете говорить о более сложных и менее базовых вещах, типа изменения длительности без изменения тона.

Ну и, самое главное. То, что вы описали, называется не «пианопьютер», а сэмплер.

P.S. То, что это перевод, вас не извиняет. Не переводите плохие статьи.
0
Goobs, #
Судя по видео с чашей, упаковка от яиц продолжает пользоваться популярностью в качестве акустического материала.
+1
Goobs, #
Если говорить вообще, мне кажется, что компьютеры недостаточно используют именно для исполнения музыки.


Эмоций Daft Punk за шлемами не видно, остальные электронщики сделали многозначительное «Пффф»
0
KvanTTT, #
На клавиатуре компьютера или ноутбука есть неприятная задержка воспроизведения звука, из-за которой нормально играть невозможно. Хотя, возможно, благодаря, драйверам это можно как-то исправить.
0
Shoonoise, #
ASIO вам поможет
+1
Beholder, #
Не понял, в чём особый смысл статьи. Сэмплированную многоголосую музыку на персональных компьютерах мы слушали ещё в начале 90-х. Причём один сэмпл не приходилось хранить в 50 вариантах, а растягивался он на лету. Причём такая программа, как Scream Tracker, если кто помнит, могла играть даже без нормальной звуковой карты, на спикер.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.