Pygame で動的に作った wave を再生してみる
なんか根本的なところで間違ったことをしているような気がしないでもないけど、とりあえずできたので。
(Python 2.6 と Pygame 1.8 用。パブリックドメイン)
from __future__ import division import wave import math import array import pygame # 作業用 wave ファイルのスペック(実際の発音は pygame の設定に依存) FILE_FREQENCY = 44100 WAVE_LENGTH = 1 DUMMY_WAVE = '__dummy__.wav' def dummy_wave(length=WAVE_LENGTH): f = wave.open(DUMMY_WAVE, 'wb') f.setparams((1, 2, FILE_FREQENCY, 0, 'NONE', 'not compressed')) f.writeframes( array.array('h', [0] * int(FILE_FREQENCY * length)).tostring()) f.close() def write_sin(buf, freqency, volume=1, length=WAVE_LENGTH): data = array.array('h', [0] * 2 * int(PYGAME_FREQUENCY * length)) a = math.pi * 2 * freqency / PYGAME_FREQUENCY vol = min(32767, 32767 * volume) for i in range(int(PYGAME_FREQUENCY * length)): data[i * 2] = data[i * 2 + 1] = int(math.sin(a * i) * vol) buf.write(data, 0) def play_sin(base_freqency, volume, length): dummy_wave() s = pygame.mixer.Sound(DUMMY_WAVE) s.play(-1) for i in range(7): m = 2 ** i freqency = base_freqency * m write_sin(s.get_buffer(), freqency, volume / m) t = pygame.time.get_ticks() print 'playing %sHz sin wave %s sec.' % (freqency, length) while (pygame.mixer.get_busy() and pygame.time.get_ticks() < t + length * 1000): pygame.time.wait(100) s.stop() if __name__=='__main__': pygame.mixer.pre_init(44100, -16, 2) pygame.init() PYGAME_FREQUENCY, format, PYGAME_CHANNELS = pygame.mixer.get_init() ## print 'PYGAME_FREQUENCY: %s' % PYGAME_FREQUENCY ## print 'format: %s' % format ## print 'PYGAME_CHANNELS: %s' % PYGAME_CHANNELS try: play_sin(110, 1, 3) finally: pygame.quit() # パブリックドメイン
いちいちファイルを作らなくても*1やれるんじゃないかという気もするし、エンディアンに依存しちゃってるようだし、*2なんかブチブチ雑音がするのも気になるし、そもそも再生中に書き換えてもいいものだか謎だしで、うーん。いまいち。
本当は Pygame や SDL のソースにまで踏み入って検証するべきなんでしょうけども。つーか Pygame(SDL) ってこういうことやるためのモジュールじゃないような感じもする。PyAudio あたりの方が妥当なのかな? でも Pygame と併用できるのか謎だし……。