Channel.queue と threading を使って途切れなく発音

左右で音程、上下で(左だけ)音量変化。

先の

はこれのためのテスト。なので波形はちゃんとつながっているみたいですから、未だにぶちぶちいうのは別の要因か?
(Python 2.5 + Pygame 1.8)

from __future__ import division
import wave
import math
import array
import threading
import Queue
import pygame
from pygame.locals import *

FULLSCREEN = False # True にするとフルスクリーン表示
FPS = 60  # 秒間描画枚数
WIDTH, HEIGHT = 640, 240  # 表示する画面のサイズ
LINEWIDTH = 20  # 線の太さ
COLOR = 0, 255, 200  # 色
BG_COLOR = 0, 0, 50  # 背景色
BASE_FREQUENCY = 220  # 左端での周波数(Hz)
OCTAVE_WIDTH = 240  # 1オクターブの幅

# 作業用 wave ファイルのスペック(実際の発音は pygame の設定に依存)
PYGAME_FREQUENCY = 22050
WAVE_LENGTH = 1/20
DUMMY_WAVE = '__dummy__.wav'

def dummy_wave(length=WAVE_LENGTH):
    f = wave.open(DUMMY_WAVE, 'wb')
    f.setparams((1, 2, PYGAME_FREQUENCY, 0, 'NONE', 'not compressed'))
    f.writeframes(
        array.array('h', [0] * int(PYGAME_FREQUENCY * length)).tostring())
    f.close()

def sins(frequency, start=0, volume=1.0, length=WAVE_LENGTH):
    left_vol = min(32767, 32767 * volume * BASE_FREQUENCY / frequency)
    right_vol = min(32767, 32767 * .5 * BASE_FREQUENCY / frequency)
    datalen =int(PYGAME_FREQUENCY * length)
    data = array.array('h', [0] * 2 * datalen)
    a = math.pi * 2 * frequency / PYGAME_FREQUENCY
    start_a = start
    for i in range(datalen):
        value = math.sin(a * i + start_a)
        data[i * 2] = int(value * left_vol)
        data[i * 2 + 1] = int(value * right_vol)
    return data, (a * datalen + start_a) % (math.pi * 2)


class MakeWave(threading.Thread):

    def __init__(self, frequency, end=0, volume=1.0):
        threading.Thread.__init__(self)
        self.setDaemon(True)
        self.frequency = frequency
        self.end = end
        self.volume = volume
        self.sound = None
        self.start()

    def run(self):
        self.wavedata, self.end = sins(self.frequency, self.end, self.volume)
        sound = pygame.mixer.Sound(DUMMY_WAVE)
        sound.get_buffer().write(self.wavedata, 0)
        self.sound = sound


def main():
    dummy_wave()
    screen = pygame.display.set_mode(
        (WIDTH, HEIGHT),
        (pygame.FULLSCREEN |
         pygame.HWSURFACE |
         pygame.DOUBLEBUF) if FULLSCREEN else 0)
    timer = pygame.time.Clock()
    channel = pygame.mixer.find_channel()
    log_base = math.log(BASE_FREQUENCY, 2)
    sound_on = False
    wavedata = None
    makewave = None
    startwave = 0
    skipframe = 0
    while True:
        screen.fill(BG_COLOR)
        x, y = pygame.mouse.get_pos()
        button1, button2, button3 = pygame.mouse.get_pressed()
        focuse = pygame.mouse.get_focused()
        a = (x / OCTAVE_WIDTH)
        frequency = 2 ** (log_base + a)
        volume = y / (HEIGHT - 1)
        if focuse:
            if button1:
                pygame.draw.line(
                    screen, COLOR, (x, 0), (x, HEIGHT - 1), LINEWIDTH)
                pygame.draw.circle(
                    screen, (255, 255, 0), (x, y), y / 2, min(3, y / 2))
                if not sound_on:
                    wavedata = None
                sound_on = True
            else:
                pygame.draw.line(
                    screen, (255, 255, 0), (x, 0), (x, HEIGHT - 1), 3)
                sound_on = False
        else:
            sound_on = False
        queue = channel.get_queue()
        if queue is None and sound_on:
            if makewave is None:
                makewave = MakeWave(frequency, startwave, volume)
            elif makewave.sound is not None:
                wavedata = makewave.wavedata
                startwave = makewave.end
                channel.queue(makewave.sound)
                makewave = None
        if sound_on and wavedata is not None:
            h = HEIGHT // 2
            pygame.draw.lines(  # 右ch
                screen, (0, 50, 100), False,
                [(x, wavedata[x * 2 + 1] * h // 32767 + h)
                 for x in range(min(WIDTH, len(wavedata) // 2))], 3)
            pygame.draw.lines(  # 左ch
                screen, (0, 100, 0), False,
                [(x, wavedata[x * 2] * h // 32767 + h)
                 for x in range(min(WIDTH, len(wavedata) // 2))], 3)
        for xb in range(0, WIDTH, OCTAVE_WIDTH):
            for s in range(12):
                x = xb + s * OCTAVE_WIDTH / 12
                pygame.draw.line(
                    screen, (253 - 23 * s, 0, 23 * s,),
                    (x, 0), (x, HEIGHT - 1), 1)
        timer.tick(FPS)
        pygame.display.flip()
        cap = '%5.2f fps, %7.2fHz, Volume%3d%%' % (
            timer.get_fps(), frequency, volume * 100)
        pygame.display.set_caption(cap)
        for event in pygame.event.get():
            if event.type == QUIT:
                return
            elif event.type == KEYDOWN and event.key == K_ESCAPE:
                return

if __name__=='__main__':
    pygame.mixer.pre_init(PYGAME_FREQUENCY, -16, 2, 2**10)
    pygame.init()
    PYGAME_FREQUENCY, format, PYGAME_CHANNELS = pygame.mixer.get_init()
    print PYGAME_FREQUENCY, format, PYGAME_CHANNELS
    try:
        main()
    finally:
        pygame.quit()
# 好きに流用してください。

Channel.queue を使って短い Sound をつなげる場合 mixerbuffersize を調整しないと音が途切れるよう。おそらく発音の長さよりもバッファが大きいとそこが無音になってしまう感じ。

これ frequency の値によっても必要なサイズが変わってしまうし、その frequency (が設定した通りになるか)はシステムに依存してしまうので……ちょっとやっかいかも。