言語ゲーム

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

Twitter: @propella

Squeak で音を扱う。

コンピュータは音を数字の連なりとして扱う。Squeak では wav ファイルやMIDI 等の決められた音源だけでなく、音声信号を直接扱う事が出来る。Squeak によってシンセサイザーを作る事も出来る。

  • 危険から身を守る

まず最初に、Squeak のスピーカから音が止まらなくなったらどうするかを紹介する。Squeakサウンド機能を完全に停止するには以下の式を使う。

SoundPlayer shutDown

Squeak で演奏する為の最も基本的な方法(最も簡単なという意味ではなく、他の複雑な方法の基礎となる手段)は、波形データを作成して、それをを元にスピーカを振動させる方法だ。音を作るためには、人間の耳に聞こえる波形データの形にする必要がある。ここでは、一番簡単な矩形波を鳴らしてみる。

sample1 := Array streamContents: [ :s |
            1 to: 400 do: [ :i | s nextPut: -5000.
                            s nextPut: 5000 ] ].
(SampledSound samples: sample1 samplingRate: 800) play

ここでは、単純に -5000 と 5000 の二つの値を交互に sample1 の中に格納し、SampledSound として演奏している。波形とは、このようにゼロを中心とする数字の連続である。数字は -32767 から +32767 のあいだを使う。この例では、1 秒間に 400 回波形を繰り返し、一秒間それを続ける。samplingRate (サンプリングレート)とは、ある音をどれくらい正確に記録するかという指標である。サンプリングレートは少なくとも周波数の倍は必要だ。(波が高いときと低いときの両方で記録する必要がある)。

音を耳で正確に確認する事は難しい。しかしここで、Squeak の重要なコンセプトである「見えない物を見る」という考え方を使えば、作り出した波形を眼で観察してみる。

WaveEditor openOn: sample1
  • サイン波

サイン波は、あらゆる音の基本となる波形である。サイン波を生成するメソッドを自分で作成するのは難しくは無いが、ここではあらかじめ用意されているメソッドを使ってサイン波を合成してみる。

wave2 := SoundPlayer sineTable: (22050/400) asInteger.
sample2 := Array streamContents: [ :s |
            1 to: 400 do: [ :i | s nextPutAll: wave2 ] ].
sin := (SampledSound samples: sample2 samplingRate: 22050) play.

sineTable: はサイン波一回分を合成する。引数は波の長さである。ここでは 400 Hzの波長を合成するために、(サンプリングレート/周波数) で波長を求めている。出来た音は後で演奏するために sin という変数に格納する。

  • フーガ

今合成したサイン波を使って、フーガを演奏してみよう。残念ながら、これには暗黙の前提が幾つかあるのだが、答えだけ言うと以下の式でフーガを演奏する事が出来る。

SampledSound defaultSampleTable: sample2. "波形を標準波形として登録"
(AbstractSound bachFugueOn: sin) play. "sin を使って演奏する。"

暗黙の前提は以下の通り。これらの値はクラス変数で操作出来る。

  1. サンプリングレートは 22050 (SoundPlayer samplingRate)
  2. 波形は 400 Hz をサンプリングした物である。 (NominalSamplePitch)
  • 課題

サイン波ではなく、矩形波を使ってフーガを演奏できるだろうか?

答え: (注意! あなたは以下の式を評価する事によって回答例を得る事が出来る。しかしその前に自分で飽きるまで実験してみる事)

(Workspace new contents: (Base64MimeConverter mimeDecodeToBytes:
'DWhhbGZTaXplIDo9ICgyMjA1MCAvIDQwMCAvIDIpIGFzSW50ZWdlci4Nc2FtcGxlMyA6PSBB
cnJheSBzdHJlYW1Db250ZW50czogWyA6cyB8DQkJCTEgdG86IDQwMCBkbzogWyA6aSB8IHMg
bmV4dFB1dEFsbDogKEFycmF5IG5ldzogaGFsZlNpemUgd2l0aEFsbDogLTUwMDApLg0JCQkJ
CQkJcyBuZXh0UHV0QWxsOiAoQXJyYXkgbmV3OiBoYWxmU2l6ZSB3aXRoQWxsOiA1MDAwKV1d
Lg1yZWN0IDo9IChTYW1wbGVkU291bmQgc2FtcGxlczogc2FtcGxlMyBzYW1wbGluZ1JhdGU6
IDIyMDUwKS4NU2FtcGxlZFNvdW5kIGRlZmF1bHRTYW1wbGVUYWJsZTogc2FtcGxlMy4NKEFi
c3RyYWN0U291bmQgYmFjaEZ1Z3VlT246IHJlY3QpIHBsYXkuDQ=='
 readStream) text contents) openLabel: 'Answer'.