この枠では、あの教材の再現をする予定だった。
しかし、大変残念なことに、自分は頭が悪く、スケジューリングが下手くそなので、開発を全く進めることができなかった。
そこで、誠に不本意ながら、取ってしまっていた枠を比較的コストが低いネタで埋めることにした。
メトロノームを作ろう
この枠では、なでしこでメトロノームを作ることにした。
今回作るメトロノームは、以下の機能を持つ。
- 決めたBPMに応じた間隔で音を鳴らす
- 決めた回数ごとに、別の音を鳴らす
音声データを用意する
なでしこ3では、「オーディオ開」や「オーディオ再生」で音声を再生することができる。
しかし、そのためには再生する音声が必要である。
今回は、「SIN」を用いて正弦波のデータを生成する。
正弦波を用意する
SINに渡す引数の係数を決める
SIN 関数は、引数で波の「位置」を指定し、そこでの値を得ることができる。
この「位置」は、
一方、音声信号をコンピュータで扱う際は、あらかじめ決めた間隔ごとの値で表現することが多い。
そこで、生成したい音声の周波数から、この間隔何個分で SIN の「位置」を一周させたいかを求める。
周波数は1秒あたり何回振動するかなので、1秒あたりの値の個数 (すなわち、サンプリング周波数) を生成したい音声の周波数 [Hz] で割ることで、これを求めることができる。
これを求めたら、この個数で SIN 関数の引数が
最後にフェードアウトをかける
単純に正弦波を出力するだけだと、信号が値 0 から離れた場所で終わることがある。
たとえば、305 Hz の信号を 0.05 秒生成すると、これが起こる。
すると、再生終了時に急激に原点に戻るためか、「プツッ」という音が鳴ってしまい汚くなる。
これの緩和を狙うため、終わりに近づくにつれて 1 から 0 になっていく値を掛け、0 に近い値で終わるようにする。
データをwavファイルにする
波形のデータを作成したら、それを音声として再生させるためのファイルに格納する。
今回は、構造が簡単で作りやすいwav形式を用いる。
wavファイルの構造
音ファイル(拡張子:WAVファイル)のデータ構造について - 福岡・東京のシステム開発会社 (株)ユーフィット
wavファイルでは、以下の構造の「チャンク」を用いる。
「チャンク」のデータとして「チャンク」を含む (「チャンク」を入れ子にする) こともある。
数値はリトルエンディアン (下位ほど前のバイトとして格納する) を用いる。
| バイト数 | データ |
|---|---|
| 4 | チャンク名 |
| 4 | チャンクのデータのバイト数 (チャンク名・バイト数は含まない) |
| 可変 | チャンクのデータ |
ここでは、「チャンク名が hoge であるチャンク」を「hoge チャンク」と呼ぶ。
今回作成するwavファイルは、以下の構造のデータの RIFF チャンクである。
- 4バイトのデータ
WAVE -
fmtチャンク -
dataチャンク
fmt チャンクのチャンク名は、fmt の3文字と半角空白1文字をこの順で結合したものである。
fmt チャンクのデータは、以下の構造である。
| バイト数 | データ |
|---|---|
| 2 | 音声フォーマット番号 (今回は 1) |
| 2 | チャンネル数 (今回は 1) |
| 4 | サンプリング周波数 (今回は 44100) |
| 4 | 1秒あたりのバイト数 (ブロックサイズ×サンプリング周波数) |
| 2 | ブロックサイズ (サンプルのバイト数 (今回は 2)×チャンネル数) |
| 2 | チャンネルのビット数 (今回は 16) |
data チャンクのデータは、サンプルの値を表すデータを順に並べたものである。
今回は、1個のサンプルを2バイトの符号付き整数 (-32,768~32,767 の範囲で、0 が中央) で表す。
今回の実装
今回は、文字列を扱う関数で処理できるため扱いやすく、JavaScript の btoa メソッド の入力にもできる、「0~255 の文字コードの文字でバイトを表す文字列」でwavファイルのデータを表現する。
整数1個をこの形式に変換する関数「整数バイナリ化」を定義した。
これは、整数 VALUE を SIZE バイトで表現する。
●(VALUEをSIZEに)整数バイナリ化とは
変数の結果は空。
位置とは変数。
位置で0から(SIZE-1)まで繰り返す。
結果は結果にCHR(AND(SHIFT_R(VALUE,8×位置),0xff))を追加。
ここまで。
結果を戻す。
ここまで。
まず、サンプルの値 (-1~1 の浮動小数点数) を格納した配列「サンプル配列」を用意する。
「配列マップ」と「配列只結合」を用いて、これを data チャンクのデータに変換する。
サンプル配列に配列マップには(値)
値に0x7fffを掛けて2に整数バイナリ化して戻す。
ここまで。
定数のサンプルデータはそれを配列只結合。
各データを配列に格納し、「配列只結合」で fmt チャンクのデータを作成する。
定数のフォーマット情報は[
1を2に整数バイナリ化, # 音声フォーマット
1を2に整数バイナリ化, # チャンネル数
サンプリング周波数を4に整数バイナリ化,
サンプリング周波数に2を掛けて4に整数バイナリ化,
2を2に整数バイナリ化, # ブロックサイズ
16を2に整数バイナリ化, # サンプルのビット数
]を配列只結合。
「連結」で各チャンクなどを作成し、wavファイルを組み立てる。
定数のdataチャンクは「data」と(サンプルデータの文字数を4に整数バイナリ化)とサンプルデータを連結。
定数のfmtチャンクは「fmt 」と(フォーマット情報の文字数を4に整数バイナリ化)とフォーマット情報を連結。
定数のRIFFデータは「WAVE」とfmtチャンクとdataチャンクを連結。
定数のwavファイルは「RIFF」と(RIFFデータの文字数を4に整数バイナリ化)とRIFFデータを連結。
音声を再生する
作成した音声データを Base64 エンコードし、data URL を経由して「オーディオ開」に渡す。
今回はwavファイルなので、data URL は data:audio/wav;base64,(Base64エンコードした音声データ) の形式である。
Base64 エンコードには、JavaScript の btoa メソッドを用いた。
なでしこの「BASE64エンコード」は、文字列を UTF-8 でエンコードしたものを Base64 エンコードする命令なので、今回の目的には使えない。
「オーディオ開」で Audio オブジェクトを取得したら、通常通り「オーディオ再生」で再生できる。
メトロノームを仕上げる
BPMと拍子の入力欄、および再生ボタンと停止ボタンを用意する。
メトロノーム音の再生を開始する際は、「秒毎」を使用し、(60÷BPM)秒ごとに音を再生する。
この命令はタイマーのIDを返すので、「それ」で回収して保存しておき、「タイマー停止」に渡すことでメトロノームを停止できる。
完成したプログラム
メトロノーム (プログラム貯蔵庫)
NG集
配列マップの関数からローカル変数を参照しようとする
音声データを、以下のように生成しようとした。
●サンプルとは
定数の周期は5。
0から9の配列連番作成。
それに配列マップには(位置)
SIN(2×PI×位置÷周期)を戻す。
ここまで。
それを戻す。
ここまで。
サンプルを表示。
このプログラムの出力は、以下のようになった。
NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN
JavaScript と違い、なでしこでは、関数のローカル変数は関数内で定義した関数も含めそれ以外の関数からは参照できない。
「配列マップ」でマップ後の値を計算するのは関数であるため、この制限に引っかかり、「周期」の値を参照できない。
複数行の式の途中の行末にコメントを書く
なでしこでは、空白と _ を用いることで、式を複数行で書くことができる。
しかし、この _ の後にコメントを書くと、エラーになるようである。
「あ」と _ # テスト
「い」を連結して表示。
[エラー][文法エラー]main.nako3(1行目): 不完全な文です。文字列『あ』、単語『_』が解決していません。
今回はこれを利用した「連結」のかわりに「配列只結合」を用いることで回避したが、範囲コメントなら書けるようである。
「あ」と /* テスト */ _
「い」を連結して表示。
おわりに
なでしこで、メトロノームを作ることができた。
なお、精度は保証しない。
特に、タブを切り替えた際、一旦止まることがあった。
大事な場面で使うなどして損害が発生しても、作者は責任を負わない。


Comments
Let's comment your feelings that are more than good