人生を豊かにする上で欠かせないのが音楽ですが、現代の音楽制作に欠かせないのがシンセサイザーです。今回は、音楽データのデジタル表現の方法を確認した後、サウンドフォント形式に対応したシンセを作ってみましょう。
音楽データのデジタル表現
音楽データと聞いて思い浮かぶのは下記のような波形データではないでしょうか。ミュージシャンのレコーディング風景に必ず映っているのが、次のような波形データです。この波形データとは一体何なのでしょうか。
そもそも、音というのは空気の振動です。湖に石を落とすと、落とした場所を中心に波紋が広がりますが、それと同じように、音は空気を振動させて音源を中心に周囲へと波が伝えるのです。
それで、コンピューターで音を扱うためには、空気の振動(アナログ情報)をデジタル化(数値化)する必要があります。空気の信号をデジタル化するには、波を一定の時間間隔で区切り、その時間ごとの波の圧力(音圧)を測定します。それで、よく見る波形データは、横軸(X座標)が時間であり、縦軸(Y座標)が音圧となっています。
音楽CDは1秒間に44,100回分の情報を記録しており、サンプリング周波数は44,100Hzとなります。これに対して、旧来の電話では1秒間に8,000回程度の情報を記録しており、サンプリング周波数は8,000Hzとなります。つまり、サンプリング周波数が大きいほど、それだけ滑らかで高音質となります。
Rustで簡単なサイン波のWAVファイルを作成しよう
それでは、音のデジタル表現について分かったところで、RustでWAVファイルを作成してみましょう。Rustのパッケージ(クレート)を集約したcrates.ioには、WAVファイルを手軽に扱うためのものが複数用意されています。今回は、手軽にWAVファイルを生成できるwav_ioを利用してみます。
ターミナルを起動して、以下のcargoコマンドを実行しましょう。cargoコマンドは、Rustのビルドシステムであり、パッケージマネージャーです。
# プロジェクトを作成
$ cargo init
# wav_ioをプロジェクトに追加
$ cargo add wav_io
すると、プロジェクトのひな形が作成されるので、src/main.rsというファイルが作成されます。そこで、このファイルを開いて次のように書き換えてみましょう。
以下のプログラムは、最もシンプルなサイン派と呼ばれる音(プーという感じの音)をプログラムで生成して「sine.wav」というWAVファイルに書き出すものです。
use wav_io;
use std::f32::consts::PI;
fn main() {
// ここで作成する波形の設定 ---- (*1)
let sample_rate = 44_100; // サンプリング周波数(CD音質)を指定
let tone = 440.0; // 周波数(Hz) A(ラ)の音
let volume = 0.6; // 音量(0-1)
// WAVファイルのヘッダを生成 --- (*2)
let mut wav_head = wav_io::new_mono_header();
wav_head.sample_rate = sample_rate; // サンプリング周波数を設定
// WAVデータを作成する --- (*3)
let sample_len = (sample_rate * 2) as usize; // 2秒間のデータを作成
let mut samples: Vec<f32> = vec![];
for i in 0..sample_len {
let c = i as f32 / sample_rate as f32;
let v = (c * tone * 2.0 * PI).sin() * volume;
samples.push(v);
}
// ファイルへ保存 --- (*4)
let mut wav_out = std::fs::File::create("./sine.wav").unwrap();
wav_io::write_to_file(&mut wav_out, &wav_head, &samples).unwrap();
}
最初にプログラムを実行してみましょう。ターミナル上で、以下のコマンドを実行します。すると「sine.wav」というWAVファイルが作成されます。
$ cargo run
Windowsならメディアプレーヤー、macOSならQuicktimeプレイヤーで再生するとプーっというサイン波が2秒再生されます。
なお、Audacityなどの波形編集ツールをインストールすると、具体的な波形を確認できます。作成したWAVファイル「sine.wav」を開いて波形を確認してみると、次の画像のように綺麗な波の波形となります。このように一定周期で繰り返すことで、安定した音階を奏でることができます。
プログラムを確認してみましょう。(*1)ではサンプリング周波数を指定します。ここでは、CD音質の44,100Hzを指定しました。また、サイン波の周波数を440.0Hzとしていますので、ラの音が書き込まれます。ここを、523.251などと変更すると、少し上のドの音になります。
(*2)ではWAVファイルのヘッダを生成します。そして、(*1)で指定したサンプリング周波数である44,100Hzを指定します。
(*3)では2秒分のサイン波を生成します。44,100Hzとはつまり1秒間に44100個のデータがあることで、2秒分なら44100*2=88200個のデータを指定することになります。なお、WAVファイルのデータには、実数(f32型)で-1.0から1.0までのデータを与えます。ここでは、f32型のsinメソッドを利用してサイン波を作成しています。
そして、(*4)でファイルへ保存します。wav_ioクレートの、write_to_file関数を使う事で、作成した波形をWAV形式でファイルに保存できます。
サウンドフォントをダウンロードしよう
ところで、プーとかポートかシンプルな音を鳴らすだけでは面白くありません。せっかくなので、ピアノやギターなどの音を鳴らしてみたいものです。今回は、伝統的なシンセサイザーの音源データであるサウンドフォント(SoundFont)を利用して、ピアノを演奏するシンセを作ってみましょう。
なお、サウンドフォントを使ってWAVファイルを作成する場合も、サイン波を計算して書き込んでいた部分、上記のプログラムで言えば(*3)の部分を、サウンドフォントを使った波形計算に変えるだけです。
インターネット上では、さまざまなサウンドフォントが配布されています。「SoundFont」あるいは「sf2/sf3」などで検索してみましょう。今回は、オープンソースのシンセデータであり、6MBという小サイズのサウンドフォント「TimGM6mb.sf2」をこちらからダウンロードして使ってみましょう。ちなみに、サウンドフォントについては、こちらに入手可能サイトの一覧がまとまっています。
RustySynthでサウンドフォントを操ろう
サウンドフォントの扱いは、それなりに大変ですが、今回は、RustySynthというクレートを利用してみましょう。ターミナルで以下のコマンドを実行して、プロジェクトを作成し、必要なクレートをインストールしましょう。
# プロジェクトを作成
$ cargo init
# クレートを追加
$ cargo add rustysynth
$ cargo add wav_io
次に「src/main.rs」を開いて、次のプログラムを記述しましょう。これは、ドミソの和音と、レファラの和音をピアノで演奏するWAVファイルを作成するプログラムです。こちらにプログラムをアップしてあります。
use std::fs::File;