第3章. 最初の完全なプログラム — 長音階の一音
この章の目的
この章を読み終える頃には、これまでの一行プログラムよりも大きな、完全なプログラムを一つ書いて実行した状態になります。プログラムは長音階のある一音の周波数を計算します。すべての細部を理解する必要はまだありません。ここで使うすべての考え方には、後の章で独立した一章が用意されています。この章の目的は、一つの完全なプログラムが端から端まで動く様子を見ることです。この章を読み終える頃には、これまでの一行プログラムよりも大きな、完全なプログラムを一つ書いて実行した状態になります。プログラムは長音階のある一音の周波数を計算します。すべての細部を理解する必要はまだありません。ここで使うすべての考え方には、後の章で独立した一章が用意されています。この章の目的は、一つの完全なプログラムが端から端まで動く様子を見ることです。
考え方: 一つの音は一つの周波数
音楽のすべての音は、1秒あたりの振動回数、ヘルツという単位で測られる周波数を持っています。ミドルCの上のAは440ヘルツで振動しています。この章ではこの音を基準にして、他のすべての音を測ります。音楽のすべての音は、1秒あたりの振動回数、ヘルツという単位で測られる周波数を持っています。ミドルCの上のAは440ヘルツで振動しています。この章ではこの音を基準にして、他のすべての音を測ります。
音を数字に結びつけるのは、二つの事実です。
- 1オクターブ上がると、周波数は2倍になります。
- 1オクターブは、半音と呼ばれる12個の等しい段に分かれます。
12個の等しい段で周波数を2倍にしなければならないので、1段ごとに周波数は同じ一定の倍率で大きくなります。その倍率は「2の12乗根」です。半音n個上昇すると、周波数は「2のn / 12乗」倍になります。12個の等しい段で周波数を2倍にしなければならないので、1段ごとに周波数は同じ一定の倍率で大きくなります。その倍率は「2の12乗根」です。半音n個上昇すると、周波数は「2のn / 12乗」倍になります。
音楽家や楽器は、よく音をMIDIナンバーと呼ばれる整数で表します。440ヘルツの基準音であるA4はMIDIナンバー69で、ミドルCはMIDIナンバー60です。MIDIナンバーmの周波数は次のようになります。音楽家や楽器は、よく音をMIDIナンバーと呼ばれる整数で表します。440ヘルツの基準音であるA4はMIDIナンバー69で、ミドルCはMIDIナンバー60です。MIDIナンバーmの周波数は次のようになります。
frequency = 440 * 2 raised to the power ((m - 69) / 12)
考え方: 長音階はステップのパターン
長音階は12個の半音すべてを使うわけではありません。ある根音(ルート)から始まり、半音ステップの一定のパターンで上昇します。長音階は12個の半音すべてを使うわけではありません。ある根音(ルート)から始まり、半音ステップの一定のパターンで上昇します。
0, 2, 4, 5, 7, 9, 11, 12
最初の音は根音そのもので、ゼロ半音上です。最後の音はオクターブで、12半音上です。間のパターンが、長音階特有の響きを生み出します。最初の音は根音そのもので、ゼロ半音上です。最後の音はオクターブで、12半音上です。間のパターンが、長音階特有の響きを生み出します。
プログラムを組み立てる
プログラムは3つの関数からできています。まずプログラム全体を読んでみてから、そのあとの解説を見てください。プログラムは3つの関数からできています。まずプログラム全体を読んでみてから、そのあとの解説を見てください。
use math::pow
fn midi_to_hz(m: Word) -> Float {
440.0 * math::pow(2.0, ((m - 69) as Float) / 12.0)
}
fn scale_degree_hz(root: Word, degree: Word) -> Float {
let steps = [0, 2, 4, 5, 7, 9, 11, 12];
midi_to_hz(root + steps[degree])
}
fn main() -> Float {
scale_degree_hz(60, 4)
}
それぞれの部分について見ていきます。
use math::powは、ホストから関数を借りる宣言です。べき乗の計算はホストの数学ライブラリが提供してくれるので、useで名前を取り込んで使えるようにします。第6章で関数に戻り、第9部でホスト関数の出所を説明します。use math::powは、ホストから関数を借りる宣言です。べき乗の計算はホストの数学ライブラリが提供してくれるので、useで名前を取り込んで使えるようにします。第6章で関数に戻り、第9部でホスト関数の出所を説明します。fn midi_to_hz(m: Word) -> Floatは、上で書いた周波数の式そのものです。MIDIナンバーmをWordとして受け取り、Floatを返します。Floatは小数部分を持てる数値型で、周波数を表すのに必要です。(m - 69) as Floatという式は整数m - 69をFloatに変換します。これは12.0で割れるようにするためです。この変換を「キャスト」と呼びます。Word、Float、キャストは第4章で扱います。fn midi_to_hz(m: Word) -> Floatは、上で書いた周波数の式そのものです。MIDIナンバーmをWordとして受け取り、Floatを返します。Floatは小数部分を持てる数値型で、周波数を表すのに必要です。(m - 69) as Floatという式は整数m - 69をFloatに変換します。これは12.0で割れるようにするためです。この変換を「キャスト」と呼びます。Word、Float、キャストは第4章で扱います。fn scale_degree_hz(root: Word, degree: Word) -> Floatは、音階のパターンをstepsという「配列」に入れています。steps[degree]は、位置degreeにある要素を読みます。関数はその半音数を根音に加えて、midi_to_hzに周波数を尋ねます。配列は第12章で扱います。fn scale_degree_hz(root: Word, degree: Word) -> Floatは、音階のパターンをstepsという「配列」に入れています。steps[degree]は、位置degreeにある要素を読みます。関数はその半音数を根音に加えて、midi_to_hzに周波数を尋ねます。配列は第12章で扱います。fn mainがプログラムを動かします。MIDIナンバー60(ミドルC)を根音とする長音階の、ディグリー4の音を尋ねています。fn mainがプログラムを動かします。MIDIナンバー60(ミドルC)を根音とする長音階の、ディグリー4の音を尋ねています。
実行する
プログラムをscale.kelという名前で保存して、実行します。
keleusma run scale.kel
出力は次のとおりです。
391.99543598174927
配列0, 2, 4, 5, 7, 9, 11, 12の位置4の値は7なので、この音はミドルCから半音7個分上の音です。この音はG4、Cメジャースケールの第5音(ドミナント)で、周波数は392ヘルツの少し手前です。プログラムはそれを最初から計算してくれました。配列0, 2, 4, 5, 7, 9, 11, 12の位置4の値は7なので、この音はミドルCから半音7個分上の音です。この音はG4、Cメジャースケールの第5音(ドミナント)で、周波数は392ヘルツの少し手前です。プログラムはそれを最初から計算してくれました。
書き換えて実行する
配列stepsには8つの要素があり、0から7まで番号が付いています。mainの中のscale_degree_hzの2番目の引数を変えて、もう一度実行してみてください。配列stepsには8つの要素があり、0から7まで番号が付いています。mainの中のscale_degree_hzの2番目の引数を変えて、もう一度実行してみてください。
- ディグリー
0は根音、つまりミドルCそのものになり、約261.63ヘルツです。 - ディグリー
7は根音の1オクターブ上で、約523.25ヘルツです。 - 間のディグリーは音階の残りの音を返します。
1番目の引数を変えれば、音階全体を別の根音に移すことができます。MIDIナンバー69なら、音階はAの上に乗ります。1番目の引数を変えれば、音階全体を別の根音に移すことができます。MIDIナンバー69なら、音階はAの上に乗ります。
このプログラムは1回の実行で1つの周波数しか返しません。なぜなら、第2章で見たように、コマンドラインツールはmainが返した一つの値だけを表示するからです。音階全体を一度に計算してそれを耳で聴くというのは、第8部のピアノロールの仕事です。このプログラムは1回の実行で1つの周波数しか返しません。なぜなら、第2章で見たように、コマンドラインツールはmainが返した一つの値だけを表示するからです。音階全体を一度に計算してそれを耳で聴くというのは、第8部のピアノロールの仕事です。
この章のまとめ
このプログラム一つで、すでに言語の多くの部分を使いました。
- パラメータと戻り型を持つ関数
Word型とFloat型- ある型から別の型へのキャスト
- 配列と、そこから要素を読み出すこと
useを使ってホストから借りる関数
これらすべてに、第4章の値と型から始まる独立した章が割り当てられています。これで一つのKeleusmaの完全なプログラムが動く様子を見たことになります。これが第1部のゴールです。これらすべてに、第4章の値と型から始まる独立した章が割り当てられています。これで一つのKeleusmaの完全なプログラムが動く様子を見たことになります。これが第1部のゴールです。