今回は簡単な波形合成を行って、レトロな電子音楽を作ってみます。プログラミングで音楽を作るというと、なんだかとても難しい計算が必要になりそうな気がします。しかし、簡単な基本さえ押さてしまえば、意外にも簡単に音を鳴らせます。今回はGo言語を使って簡単なサイン波を生成しWAVファイルに書き出してみます。

  • プログラムで電子音楽を奏でよう

    プログラムで電子音楽を奏でよう

音楽ファイルについて

最近では、ストリーミングで音楽を聴くことが増えています。すると、どのようなデータが音楽データとしてやり取りされているのか、あまり意識することがないかもしれません。それでも、意識しないだけで、配信サーバーから音楽データがプレイヤーの端末にダウンロードされて再生されています。

そもそも音というのは空気の振動です。この振動をデジタル信号で記録したものが音楽データです。つまり、音をデジタル信号で表した波形データとなります。それで、素の波形データを素直に記録したファイル形式がWAV形式(あるいはWAVE形式)です。

WAVファイルは、簡単なヘッダ情報と波形データから構成された単純な形式なので、Windows/macOSなど主要OSで再生が可能です。そして、プログラミングで簡単にWAVファイルを生成できます。つまり、それほど複雑な手順を踏むことなく、気軽に電子音楽を作成できるのです。

簡単なサイン波を鳴らしてみよう

ちなみに、とにかくただ音を鳴らしたいだけなら、でたらめなデータを波形データとして書き込むことでも音を鳴らすことができます。ただし、この場合、ザーッというノイズが再生されてしまいます。

それなりに、音楽を奏でるためには、簡単な規則を持っていなくてはなりません。そこで、最初に、ピーっという簡単なサイン波を生成するプログラムを紹介します。

WAVファイルを手軽に生成するために、今回、go-wavというライブラリを使ってみましょう。最初に、コマンドラインで以下のコマンドを実行して、go-wavをインストールしておきましょう。

$ go mod init example.com/test
$ go get github.com/youpy/go-wav

そして、次のようなプログラムを作成します。以下のプログラムは、乱数を利用してでたらめな波形データを作成するプログラムです。

package main
import (
    "math"
    "os"
    "github.com/youpy/go-wav"
)

func main() {
    // 出力ファイルを指定する --- (*1)
    file, _ := os.Create("sine.wav")
    defer file.Close() // 最後に自動的にファイルを閉じる
    // サンプルレートを指定 --- (*2)
    const sampleRate = 44100.0
    length := int(sampleRate * 3) // 3秒分の波形データ
    // WAVヘッダを準備する --- (*3)
    writer := wav.NewWriter(file, uint32(length), 1, uint32(sampleRate), 16)
    // サイン波を生成する --- (*4)
    tone := 440.0
    volume := 0.6
    samples := make([]wav.Sample, length)
    for i := 0; i < length; i++ {
        v := math.Sin((float64(i) / sampleRate) * tone * 2.0 * math.Pi)
        samples[i].Values[0] = int(v * 0x7FFF * volume)
    }
    // データを書き込む --- (*5)
    writer.WriteSamples(samples)
}

上記のプログラムを「make_sine.go」という名前で保存しましょう。そして、以下のコマンドを実行します。

$ go run make_sine.go

すると「sine.wav」という名前のWAVファイルが生成されます。音楽プレイヤーでこのWAVファイルを再生できます。ただし、大きな音が鳴るので、音量を小さ目にしてから再生してください。

なお、音声編集ソフトのAudacityを利用して作成したWAVデータを読み込んでみると、次のように、でたらめなデータが生成されているのが確認できます。

  • Audacityで作成したWAVファイルの波形を確認しているところ

    Audacityで作成したWAVファイルの波形を確認しているところ

プログラムを確認してみましょう。(*1)では「sine.wav」というファイルを生成します。そして、(*2)ではサンプルレートを指定します。サンプルレートというのは1秒間にいくつの波形データを指定するかを意味します。44100という値は音楽業界で一般的に使われる値です。

(*3)ではWAVファイルを書き出すために、wav.Writerオブジェクトを生成します。引数には、WAVファイルのヘッダ情報を指定します。そして、(*4)ではmath.Sin関数を利用してサイン波を生成して、(*5)でファイルに書き出します。

カエルの歌を生成してみよう

上記のプログラムでは、周波数440Hz(ラの音)のサイン波を生成しました。この周波数を261.6Hzにするとドの音を鳴らすことができます。つまり、サイン波の周波数さえ変えてしまえば、音階を演奏できるということです。

そして、この周波数を半分にすると1オクターブ低い音になり、周波数を二倍にすると1オクターブ高い音になります。一般的に音階はドレミファソラシと半音階を含めた12の音で1オクターブを表現します。つまり、以下の計算で周波数を求めることが可能です。(なお、式に出てくるnoteNoはピアノの鍵盤を左側から0,1,2...番と番号をつけたものです。)

> 周波数 = 440.0 * math.Pow(2.0, (noteNo - 69.0) / 12.0)

それでは、かえるの歌(ドイツ民謡で正式には「かえるの合唱」)をGoのプログラムで演奏してみましょう。

package main
import (
    "math"
    "os"
    "github.com/youpy/go-wav"
)
// 定数を設定
const sampleRate = 44100.0 // サンプルレートを指定
const bpm = 120 // テンポを指定
func main() {
    file, _ := os.Create("kaeru.wav")
    defer file.Close()
    len4 := int(sampleRate * 60 / bpm) // 四分音符の長さ --- (*1)
    // かえるの歌の音階を指定 --- (*2)
    // ド:60 レ:62 ミ:64 ファ:65 ソ:67 ラ:69 シ:71
    notes := []int{
        60, 62, 64, 65, 64, 62, 60, 60,
        64, 65, 67, 69, 67, 65, 64, 64,
        60, 0, 60, 0, 60, 0, 60, 0,
        60, 62, 64, 65, 64, 62, 60, 0,
    }
    // WAVファイルの設定を指定 --- (*3)
    sampleSize := uint32(len(notes) * len4)
    writer := wav.NewWriter(file, sampleSize, 1, uint32(sampleRate), 16)
    for i := 0; i < len(notes); i++ {
        // 各音符を書き込む
        addNote(writer, notes[i], len4)
    }
}
// 音符を書き込む --- (*4)
func addNote(writer *wav.Writer, note int, length int) {
    // 音階から周波数を計算 --- (*5)
    tone := 440.0 * math.Pow(2.0, float64(note-69)/12.0)
    // サイン波を書き込む
    samples := make([]wav.Sample, length)
        for i := 0; i < length; i++ {
            v := math.Sin((float64(i) / float64(sampleRate)) * tone * 2.0 * math.Pi)
            v *= 0.3 // 音量を下げる
        // プチッと切れるノイズを軽減 --- (*6)
            decLen := length / 16 // 音符の最後の音量を下げる
        if i > length - decLen {
                v *= float64((length - i)) / float64(decLen)
        }
            samples[i].Values[0] = int(v * 0x7FFF)
    }
    writer.WriteSamples(samples)
}

上記プログラムを「kaeru.go」という名前で保存したらプログラムを実行してみましょう。

なお、先ほど作成した「make_sine.go」と同じフォルダに入れても正しく動かないので、改めて別のフォルダを作成し、go-wavをインストールする作業を実行してください。その後、以下のコマンドを実行するとGoのプログラムが実行されます。

$ go run kaeru.go

そして「keru.wav」というファイルが作成されたら音楽プレイヤーで再生してみましょう。かえるの歌がが流れるでしょう。

プログラムを確認してみましょう。(*1)では音楽を奏でるために、bpmやsampleRateから基本となる四分音符の長さを計算します。そして、(*2)ではかえるの歌の音階を指定します。(*3)ではWAVファイルのヘッダを指定して、wav.Writerオブジェクトを生成します。

(*4)では指定された音階で1音分のデータを書き込みます。(*5)ノート番号(鍵盤の番号)から音階の周波数を計算してサイン波を書き込みます。なお(*6)では音符と音符の間に入るプチッというノイズを軽減するために、1音の末尾の音量を小さくする処理を入れています。この部分をコメントアウトして音の違いを確かめてみてください。

また、(*4)の音階を変更することで、かえるの歌以外の曲を演奏することもできます。番号を書き換えて好きな曲を生成するように書き換えてみてください。なお、0を指定すると休符になります。

まとめ

以上、今回は簡単な音楽を生成するプログラムを紹介しました。サイン波はGo言語の三角関数math.Sin関数を使うと比較的簡単に作れます。これを利用することで、シンプルながら任意の高さの音楽を演奏できました。このように、音楽ファイルの生成はそれほど難しいものではないので、ぜひ挑戦してみてください。

自由型プログラマー。くじらはんどにて、プログラミングの楽しさを伝える活動をしている。代表作に、日本語プログラミング言語「なでしこ」 、テキスト音楽「サクラ」など。2001年オンラインソフト大賞入賞、2004年度未踏ユース スーパークリエータ認定、2010年 OSS貢献者章受賞。技術書も多く執筆している。直近では、「シゴトがはかどる Python自動処理の教科書(マイナビ出版)」「すぐに使える!業務で実践できる! PythonによるAI・機械学習・深層学習アプリのつくり方 TensorFlow2対応(ソシム)」「マンガでざっくり学ぶPython(マイナビ出版)」など。