言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

Squeak のローレベルサウンド API

昨日少し書いた Squeakサウンド機能ですが、意図を書いてませんでした。Squeakサウンド機能を使うときに、なんだか汚いとか、うまく行かないという事は無いでしょうか? etoys で使えるサンプリング音は論外として、Smalltalk を使ってのサウンド出力においても、何となくうまく行かない事が多いのです。具体的に私が遭遇したのは、

  1. 音を三つほど重ねるとガリガリと雑音のような音が入る。
  2. 音を鳴らそうと思った時に鳴らない。

の二つの組み合わせのようです。これでは、Squeak の音はちょっとした効果音や BGM 程度にしか使えません。私はセレロン300 マシンの頃から Squeak で遊んでますが、今やギガが当たり前の世の中で未だ改善されないのはどうした事でしょう?! これが限界だとはとても思えないので、もう少し中をのぞいてみました。

Squeak で音を鳴らすための最も原始的なコードを書きます。ここでは、buf に 矩形波を入れて出力しています。波形データは符号付 16ビットの範囲です。このように、#primSoundStartBufferSize:rate:stereo: でサンプリングレート等を設定し、#primSoundPlaySamples:from:startingAt: で実際のデータを与えます。この順序を間違えるとすぐ落ちます。

SoundPlayer initialize; shutDown; startUp.
buf := SoundBuffer streamContents: [:s |
    200 timesRepeat: [
        10 timesRepeat: [s nextPut: 2000].
        10 timesRepeat: [s nextPut: -2000] ] ].
SoundPlayer primSoundStartBufferSize: buf stereoSampleCount rate: 22050 stereo: true.
SoundPlayer primSoundPlaySamples: buf stereoSampleCount from: buf startingAt: 1.

さて、これを使って昨日のランダム音楽を書いてみると、その差に歴然とします。面白いので是非 do it してみてください。Squeak の標準 SoundPlayer では、バッファサイズが大きいため、リアルタイムな細かい音の変化を表現出来ていなかった事が分かります。ようするに Squeakテルミンみたいな物が作れないのはこのせいでした。

SoundPlayer initialize; shutDown; startUp.
length := 0.05.
rate := 22050.
buf := SoundBuffer newStereoSampleCount: (rate * length) asInteger.
SoundPlayer primSoundStartBufferSize: buf stereoSampleCount rate: rate stereo: true.
[
    pitch := 40 atRandom + 10.
    1 to: buf size do: [:i | 
        buf at: i put: (((i / pitch) fractionPart > 0.5)
            ifTrue: [ 2000]
            ifFalse: [-2000])].
    Transcript cr; show: pitch.
    SoundPlayer primSoundPlaySamples: buf stereoSampleCount from: buf startingAt: 1.
    [SoundPlayer primSoundAvailableBytes > 0] whileFalse.
] repeat.

以下は、SoundPlayer による普通のランダム音楽プログラムです。昨日のコードを読みやすさの為変数名などをそろえて読みやすくしました。

length := 0.05.
rate := 22050.
buf := SoundBuffer newStereoSampleCount: (rate * length) asInteger.
[
    pitch := 40 atRandom + 10.
    1 to: buf size do: [:i | 
        buf at: i put: (((i / pitch) fractionPart > 0.5)
            ifTrue: [ 2000]
            ifFalse: [-2000])].
    Transcript cr; show: pitch.
    (SampledSound  samples: buf samplingRate: rate) play.
    (Delay forSeconds: length) wait.
] repeat.

結論としては、プリミティブを使えば割と意図した音を鳴らす事が出来るような気がするのですが、すぐに落ちるという欠点があるので、プリミティブをラップするような低レベルの API が必要なのではないかと思います。