前回、サむン波を利甚しお音楜ファむルを生成するプログラムを䜜りたした。前回のプログラムでは、音階を番号で指定しなくおはならず、曲デヌタを入力が倧倉でした。そこで、前回のプログラムにブラりザから䜿えるUIを぀けお誰でも䜿えるオルゎヌルを䜜っおみたしょう。

  • ブラりザで䜿えるオルゎヌルアプリを䜜っおみよう

    ブラりザで䜿えるオルゎヌルアプリを䜜っおみよう

オルゎヌルの仕組み

今回䜜成するのは、ブラりザで曲デヌタの入力ができるようなオルゎヌルです。ブラりザの画面でオルゎヌルのピンを指定するこずで、手軜に曲デヌタを䜜成できるようにしたす。

倧きな仕組みずしおは、Go蚀語でWebアプリを䜜成したす。アプリの仕組みを図にするず、以䞋のようになりたす。

  • 今回䜜るオルゎヌルの仕組み

    今回䜜るオルゎヌルの仕組み

詳しく芋おみたしょう。たず、Go蚀語でHTTPサヌバヌを起動したす。ブラりザでサヌバヌにアクセスするず、オルゎヌルの画面HTMLを返信したす。そしお、ナヌザヌはブラりザ䞊でオルゎヌルの曲デヌタを入力したす。そしお、再生ボタンを抌すず、Ajaxで曲デヌタをサヌバヌに送信し、Goのサヌバヌ偎でWAVファむルを生成したす。WAVファむルが生成されたら、ブラりザ偎でWAVファむルを読み蟌んで再生したす。

なお、WAVファむルの生成郚分は前回䜜成したプログラムをそのたた利甚したす。そのため、ブラりザ䞊で䜜成する曲デヌタは、ピアノの鍵盀を番号で衚した番号(ノヌト番号)をカンマで区切っお指定したものずしたす。この時、0番を指定するず䌑笊を指定したものずみなしたす。

䟋えば、「ドレミヌドレミ゜」ずいうデヌタであれば「60,62,64,0,60,62,64,0」ずいうデヌタになりたす。なお、ブラりザの開発者ツヌルでコン゜ヌルを衚瀺するず、どんな曲デヌタを生成したのかを確認できるようにしおいたす。

  • ブラりザの開発者ツヌルでコン゜ヌルを衚瀺するず曲デヌタを確認できる

    ブラりザの開発者ツヌルでコン゜ヌルを衚瀺するず曲デヌタを確認できる

プロゞェクトを䜜ろう

それでは、前回ず同じように、WAVファむルを手軜に生成するために、go-wavずいうラむブラリをむンストヌルしたしょう。たた、ブラりザでオルゎヌルを動かすために、WebフレヌムワヌクのGinもむンストヌルしたしょう。

最初に、コマンドラむンで以䞋のコマンドを実行しお、go-wavずGinをむンストヌルしたしょう。

$ go mod init example.com/musicbox
$ go get github.com/youpy/go-wav
$ go get github.com/gin-gonic/gin

なお、今回、WAVファむルを生成するサヌバヌ偎のGoのプログラム「musicbox.go」ず、ピンを眮くHTML画面「index.html」ず二぀のファむルで䜜りたす。以䞋のようなファむル構成になるようにしたす。プロゞェクト䞀匏はこちらからダりンロヌドできたす。

.
├── go.mod  --- Goのモゞュヌル蚭定ファむル(自動的に䜜られる)
├── musicbox.go  --- オルゎヌルのプログラム
└── static
    ├── index.html  --- オルゎヌルの画面
    └── box.wav  --- WAVファむル(自動的に䜜られる)

WAVファむル生成サヌバヌを䜜ろう

それでは、たずは、Go蚀語でWAVファむルを生成するHTTPサヌバヌから芋おいきたしょう。以䞋のプログラムを「musicbox.go」ずいう名前で保存したしょう。

package main
import (
    "log"
    "math"
    "net/http"
    "os"
    "strconv"
    "strings"
    "github.com/gin-gonic/gin"
    "github.com/youpy/go-wav"
)
const sampleRate = 44100.0 // サンプルレヌトを指定
const bpm = 120            // テンポを指定
func main() {
    // サヌバヌの蚭定
    router := gin.Default()
    // オルゎヌルの画面(HTMLファむル)を返す --- (*1)
    router.StaticFS("/static", http.Dir("static"))
    router.GET("/", func(ctx *gin.Context) {
        ctx.Redirect(302, "/static/index.html")
    })
    // WAVファむル生成API --- (*2)
    router.GET("/make", func(ctx *gin.Context) {
        notesStr := ctx.Query("notes")
        notes := strings.Split(notesStr, ",")
        file, _ := os.Create("static/box.wav")
        defer file.Close()
        len4 := int(sampleRate * 60 / bpm)
        sampleSize := uint32(len(notes) * len4)
        writer := wav.NewWriter(file, sampleSize, 1, uint32(sampleRate), 16)
        for i := 0; i < len(notes); i++ {
            // 各音笊を曞き蟌む
            no, _ := strconv.Atoi(notes[i])
            addNote(writer, no, len4)
        }
        ctx.String(200, "ok")
    })
    // サヌバヌを起動 --- (*3)
    err := router.Run("127.0.0.1:8888")
    if err != nil {
        log.Fatal("サヌバヌ起動に倱敗", err)
    }
}
// 音笊を曞き蟌む --- (*4)
func addNote(writer *wav.Writer, note int, length int) {
    // 音階から呚波数を蚈算
    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 // 音量を䞋げる
        // プチッず切れるノむズを軜枛
        decLen := length / 8 // 音笊の最埌の音量を䞋げる
        if i > length-decLen {
            v *= float64((length - i)) / float64(decLen)
        }
        samples[i].Values[0] = int(v * 0x7FFF)
    }
    writer.WriteSamples(samples)
}

簡単にプログラムの説明をしたす。(1)の郚分では、オルゎヌルの画面(HTML)が衚瀺されるようにリダむレクトを蚭定したす。たた、staticディレクトリ以䞋に配眮したファむルに自由にアクセスできるようにしたす。

(2)では「/make」ずいうURLにアクセスした時、WAVファむルが生成されるようにしたす。URL匕数のnotesに指定した曲デヌタを取埗し、その曲デヌタに沿っおWAVファむルを生成したす。生成が完了したら、okずだけ短くレスポンスを返したす。

(3)ではサヌバヌを起動したす。ここでは、ポヌト8888でHTTPサヌバヌを起動したす。WebフレヌムワヌクのGinを利甚しおいるため、非垞に簡朔に各皮凊理を蚘述できたした。なお、Ginに関しおは、本連茉の17回目で詳しく解説しおいるので参考にしおください。

オルゎヌル画面を䜜ろう

続いお、オルゎヌル画面を䜜成したしょう。staticずいうフォルダを䜜成し、以䞋のHTMLを「index.html」ずいう名前で保存したす。オルゎヌルのピンは、HTMLのラゞオボックスをそのたた利甚しおいたす。

<html><meta charset="utf-8">
<body>
    <h1>オルゎヌル<button onclick="play()">再生</button></h1>
    <div id="box"></div>
</body>
<script>
    const MAX_ROWS = 32
    // オルゎヌル画面を䜜成(ラゞオボックスをたくさん䜜成) --- (*1)
    const box = document.getElementById('box')
    const colors = [0,1,0,1,0,0,1,0,1,0,1,0]
    let html = ''
    for (let row = 0; row < MAX_ROWS; row++) {
        let cols = ''
        for (let col = 0; col < 24; col++) {
            let bg = (colors[col % 12]) ? 'background-color:#CCC;' : ''
            switch (row % 8) {
            case 3: bg += `border-bottom:2px solid silver;`; break;
            case 7: bg += `border-bottom:2px solid gray;`; break;
            }
            const va = 60 + col
            cols += `<td style="${bg}border-left:1px solid #999;">`
            cols += `<input type="radio" id="g${row}" name="g${row}" value="${va}">`
            cols += `</td>`
        }
        html += `<tr>${cols}</tr>`
    }
    box.innerHTML = `<form id="boxform"><table>${html}</table></form>`
    // 再生ボタンを抌した時の凊理
    function play() {
        // オルゎヌルデヌタを䜜成 --- (*2)
        const res = []
        const form = document.getElementById('boxform')
        for (let i = 0; i < MAX_ROWS; i++) {
            let v = form[`g${i}`].value
            if (!v) v = 0
            res.push(v)
        }
        const s = res.join(',').replace(/(,0)+$/, '')
        console.log('notes=', s)
        // サヌバヌに送信 --- (*3)
        fetch('/make?notes=' + s).then(r => r.text()).then((text) => {
            console.log('response=', text)
            const audio = new Audio('/static/box.wav?r=' + s)
            audio.currentTime = 0
            audio.play()
        })
    }
</script>
</html>

簡単にHTML/JavaScriptに぀いお玹介したす。オルゎヌルのピンはラゞオボックスをたくさん衚瀺しお実珟しおいたすが、(1)の郚分でHTMLをJavaScriptによっお動的に生成しおいたす。

そしお、再生ボタンを抌した時、(2)ラゞオボックスからピンの刺された郚分の曲デヌタを取り出したす。それから、(3)でfetch APIを利甚しおサヌバヌに曲デヌタを送信したす。レスポンスが返っおきたら、WAVファむルを再生するようにしたす。

オルゎヌルを実行しよう

それでは、プログラムを実行しおみたしょう。プログラムを実行するには、コマンドラむンで以䞋のコマンドを実行したす。

$ go run .

するず、HTTPサヌバヌが起動するので、ブラりザで「http://localhost:8888」にアクセスしたす。するずオルゎヌルの画面が衚瀺されたす。暪方向が音皋で、瞊方向が時間ずなっおいたす。音皋は巊から、ドドレレミファ゜・・・ずピアノの鍵盀のように䞊んでいたす。ピンを刺したら画面䞊郚の「再生」ボタンを抌したす。するず、WAVファむルが生成され、音楜が再生されたす。

  • localhost:8888にアクセスするずオルゎヌルの画面が出る

    localhost:8888にアクセスするずオルゎヌルの画面が出る

たずめ

以䞊、今回はオルゎヌルのプログラムを䜜っおみたした。特に、前回䜜成したWAVファむルの生成プログラムをちょっず改良しお、HTML/JavaScriptで画面を䜜っおみたした。GoのプログラムずHTMLファむルを足しおも115行しかありたせん。芋通しが良いので、この手のプログラムを䜜る参考になるこずでしょう。

ただし、実行しおみるず分かりたすが、オルゎヌルず呌ぶにはあたりにも再生される音が玠朎ですよね。WAVファむルを生成する凊理を改良しお、より良い音が鳎るようにいろいろ改良しおみるず良いでしょう。実際に音が鳎る音楜プログラミングは面癜いので、ぜひ挑戊しおみおください。

自由型プログラマヌ。くじらはんどにお、プログラミングの楜しさを䌝える掻動をしおいる。代衚䜜に、日本語プログラミング蚀語「なでしこ」 、テキスト音楜「サクラ」など。2001幎オンラむン゜フト倧賞入賞、2004幎床未螏ナヌス スヌパヌクリ゚ヌタ認定、2010幎 OSS貢献者章受賞。技術曞も倚く執筆しおいる。盎近では、「シゎトがはかどる Python自動凊理の教科曞(マむナビ出版)」「すぐに䜿える!業務で実践できる! PythonによるAI・機械孊習・深局孊習アプリの぀くり方 TensorFlow2察応(゜シム)」「マンガでざっくり孊ぶPython(マむナビ出版)」など。