Pygame で動的に作った wave を再生してみる

screenshot
なんか根本的なところで間違ったことをしているような気がしないでもないけど、とりあえずできたので。
(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なんかブチブチ雑音がするのも気になるし、そもそも再生中に書き換えてもいいものだか謎だしで、うーん。いまいち。

本当は PygameSDL のソースにまで踏み入って検証するべきなんでしょうけども。つーか Pygame(SDL) ってこういうことやるためのモジュールじゃないような感じもする。PyAudio あたりの方が妥当なのかな? でも Pygame と併用できるのか謎だし……。

*1:もちろん Numeric とかも使わずに

*2:ソース読んだらちゃんと処理してるっぽい