ポルタメントしてみる

図形を描くのは後回し。楽器ならまず音を出せないとね。

の応用で、ドラッグ操作でリアルタイムに音を出しポルタメントできるものを作ってみました。
(Python 2.6 + Pygame 1.8)

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

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

# 作業用 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 set_wave(sound, frequency):
    write_sin(sound.get_buffer(), frequency, BASE_FREQUENCY / frequency)

def main():
    dummy_wave()
    sound = pygame.mixer.Sound(DUMMY_WAVE)
    screen = pygame.display.set_mode(
        (WIDTH, HEIGHT),
        (pygame.FULLSCREEN |
         pygame.HWSURFACE |
         pygame.DOUBLEBUF) if FULLSCREEN else 0)
    timer = pygame.time.Clock()
    sound_on = False
    log_base = math.log(BASE_FREQUENCY, 2)
    old_x = -1
    while True:
        timer.tick(FPS)
        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)
        if focuse:
            if button1:
                pygame.draw.line(
                    screen, COLOR, (x, 0), (x, HEIGHT - 1), LINEWIDTH)
                if sound_on:
                    if old_x != x:
                        set_wave(sound, frequency)
                        old_x = x
                else:
                    set_wave(sound, frequency)
                    old_x = x
                    sound.play(-1)
                    sound_on = True
            else:
                pygame.draw.line(
                    screen, (255, 255, 0), (x, 0), (x, HEIGHT - 1), 3)
                sound.stop()
                sound_on = False
                old_x = -1
        else:
            sound.stop()
            sound_on = False
            old_x = -1
        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)
        pygame.display.flip()
        cap = '%5.2f fps, %7.2fHz' % (
            timer.get_fps(), frequency)
        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(44100, -16, 2)
    pygame.init()
    PYGAME_FREQUENCY, format, PYGAME_CHANNELS = pygame.mixer.get_init()
    try:
        main()
    finally:
        pygame.quit()
# 好きに流用してください。

やっぱりブチブチ言うしそれに反応も怪しい。リアルタイムに連続して音程を変えられる音源の作り方をもう少し工夫する必要がありそう。

とはいえ実用品を目指すのではなくモックアップでいいならこんなのでもかまわないか。とりあえずこれのまま続けてみるかな。