http://habrahabr.ru/post/225745/
От переводчика:
Статья, которую я предлагаю вам почитать, не нова — она опубликована аж 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 строк на Питоне!
Даже дешевый компьютер имеет достаточно элементов управления, чтобы стать полноценной музыкальной станцией: можно петь в микрофон, показывать жесты через веб-камеру, модулировать всякие штуки мышкой и управлять остальным с клавиатуры. Столько средств самовыражения, и для каждого есть библиотека на Питоне… Артистичные хакеры, никто не желает шагнуть в эту сторону?