Пітон, зміщення тону і Піанопьютер

      Від перекладача:
 
Стаття, яку я пропоную вам почитати, не нова — вона опублікована аж 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 рядків на Пітоні!
 
Навіть дешевий комп'ютер має достатньо елементів управління, щоб стати повноцінною музичної станцією: можна співати в мікрофон, показувати жести через веб-камеру, модулювати всякі штуки мишкою і управляти рештою з клавіатури. Стільки коштів самовираження, і для кожного є бібліотека на Пітоні… Артистичні хакери, ніхто не бажає зробити крок у цей бік?
  
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.