How to Make
The Piano

このスライドのURL

https://t.co/pbQ5rYFCMR

注意:音がなります。

音量に注意(イヤホン推奨)

(たぶんIEの人は見れないと思う・・・)

お前誰よ

名前:入江田 昇

H/N: @nobonobo

会社: 「あ」のつく会社

分類: メカトロソフト屋

主な利用言語: Verilog/C/Python/Go

趣味: インコ観察

これまでの
活動(1)

Python情報サイトの運営

PythonMatrixJp

これまでの
活動(2)

これまでの
活動(3)

Go言語情報サイトの運営

GolangRdyJp

これまでの
活動(4)

これまでの
活動(5)

GoConWinter2015で発表

gomobileでピアノの作り方 <-now!

お前誰よ

おわり

モチベーション

一般の
ピアノアプリ

子供が大好き

子供が遊ぶ

謎機能起動

戻す

子供が遊ぶ

広告起動

戻す

子供が遊ぶ

ソーシャル
機能起動

戻す

イライラ!

イライラ!

もっと

シンプルな

ピアノが

欲しい〜!

とか考えてたら

Go Challenge 7

というイベントが!

しかもお題がピアノモバイルアプリ!

これは是非とも
参加しなくては!

というわけで

gomobileで
ピアノの作り方

アジェンダ

  • 音に関する物理

  • ピアノの仕組み

  • デジタルオーディオ用語

  • gomobileでアプリを作る

以上の構成です

今日はGoのカンファレンスに
来ておきながら、
Goの成分少なめです。

まずは一緒に高校物理の復習をしましょう。

Dive into Physics

音の原理を学ぼう!

音ってなに?

音波に乗って伝搬するもの

ですね

音波ってなに?

  • 空気の気圧の疎密波

  • 縦波の一種

  • 音速で進行する進行波

ですね

波の三要素

  • 周波数

  • 振幅

  • 波形

ですね

音楽って?

色んな音色の楽器を

組み合わせて音量や音階を

変化させて構成されたもの

ですね

音の三要素

  • 音階(周波数)

  • 音量(振幅)

  • 音色(波形)

ですね

音階の解説

音階って?(1)

ド(C) レ(D) ミ(E) ファ(F)
ソ(G) ラ(A) シ(B)

8音程目で一巡するので
オクターブ

音階って?(2)

ラ=440Hzを基準に

  • 1オクターブ上のラは880Hz

  • 1オクターブ下のラは220Hz

オクターブの物理上の意味は
周波数で2倍の乗数のこと

音階って?(3)

ピアノの鍵盤を見ると・・

音階って?(4)

黒鍵を含めて
1オクターブに鍵は12個

隣り合う鍵の周波数は

「2の12乗根」の

等比数列になっていて

12平均律といいます

音階って?(4)

周波数との関係は以下の式

n: 音階番号

f: 周波数(Hz)

で表せます

音階番号

MIDIでは

(Musical Instrument Digital Interface)

音階に番号を振って

ラ=440Hz=69番

これをノートナンバーと呼びます

ノートナンバー

ピアノに当てはめると

音階について

おわり

音量の解説

単音の音量変化

  • 打楽器は急に音量が上下します

  • 菅楽器は緩やかに音量が上下します

また単音のことをノートとも言います

ノートって?

MIDIの音を鳴らす

最小単位を

「ノート」と言い

ひとつの音符に対応します

ノート音量変化

変化の形状パターンを

「エンベロープ」

といいます

エンベロープ(1)

鳴らそう=ノートオンから

鳴らすのを止める=ノートオフで

それに伴う音量変化の形状

エンベロープ(2)

以下の値で模式化

エンベロープ(3)

色んな楽器を模すのに

このくらいパラメータが

あれば大まかに似せられるでしょうという

音量変化近似モデル

音量について

おわり

音色の解説

音色って?

音階と音量が同じでも

違うと感じる特色のこと

≒波形

サイン波

矩形波

三角波

ノコギリ波

いろいろあるけど

実は・・・

サイン波の合成

だけで

多彩な波形を表現できます

矩形波を周波数解析

左から1,3,5,7...倍の
周波数成分を持っています

奇数倍サイン波

を加算合成すると?

矩形波っぽくなった!

つまり・・・

周波数解析結果にあわせて

サイン波を足しこめば

近い音色が作れる!

というわけです

音色について

おわり

Dive into The Piano

ピアノの構造

弦を叩く打楽器の一種

弦の揺れ方(1)

定在波または定常波

弦の揺れ方(2)

2倍、3倍の振動

ピアノの工夫

一つの音階に複数の弦

倍音をよく含むように
調律師による調整

弦の固定も3セクションあったり

ピアノの音って?

再生!

再現するには?

戦略は2通り

  • 実際の音をサンプル

  • 計算で再現

機材も環境もないので
当然のように後者ですね

ピアノの音解析

再生!

どうやら

1,2,3,4...倍周波数で

上に行くほど弱い成分を

もっているようです

音色(波形)

こんな感じでどうだろう

ピアノ音全体

エンベロープ

こんな感じでどうだろう

これまでに決めた

  • 音階の式と

  • 波形の式と

  • エンベロープの形

がピアノの音実現の目標です

Dive into
Digital Audio

デジタルオーディオの
用語解説

記録と再生

物理現象である音波を

どうやって記録して

どうやって再生するの?

マイク

音波(疎密波)を受けて

電力に変換する

デバイス

A/D Converter

アナログ電圧を

数値に変換する

デバイス

D/A Converter

数値に比例した

アナログ電圧を出力する

デバイス

スピーカー

電力を受けて

音波(疎密波)に変換する

デバイス

デジアナの境界

サンプリングレート

時間軸における記録の密度で
常にこれが何Hzかを
意識することは大変重要

特に注釈のない「Fs」は
サンプリングレートのこととします

また逆数「1/Fs」を周期「Td」と言います

サンプルの分解能

16bitがメジャー

記録方式

PCM方式が基本

チャンネル

今回はモノラルなので1チャンネルのみ

用語解説

おわり

Dive into GOMOBILE

さあコードに落としこもう!

gomobileって?

goでモバイルアプリを作るSDK


      go get -u golang.org/x/mobile/cmd/...
      gomobile init
      

これだけで準備完了!

audioサンプル

デスクトップ版ビルドと実行


      cd $GOPATH
      cd src/golang.org/x/mobile/example/audio
      go build .
      ./audio
      

動いた?

最小のメイン


    func main() {
      app.Main(func(a app.App) {
        for e := range a.Events() {
          switch e := a.Filter(e).(type) {
          case lifecycle.Event:
            switch e.Crosses(lifecycle.StageVisible) {
            case lifecycle.CrossOn: ...
            case lifecycle.CrossOff: ...
            }
          case size.Event: ...
          case paint.Event: ...
          }
        }
      })
    }
    

lifecycle重要

CrossOn: アプリの起動or復旧

CrossOff: アプリの停止or終了

ちゃんとリソースを
構築&解放しましょう

実際のコード解説

Oscillator


    type Oscillator func() float32
    

サンプル値を返すもの

という抽象化概念を定義

呼び出すとTd秒進む

オペレータの定義


    func GenOscillator(freq float32) Oscillator {...}
    func G(gain float32, f Oscillator) Oscillator {...}
    func Multiplex(fs ...Oscillator) Oscillator {...}
    

源発振生成関数

ゲインオペレータ

マルチプレクサ(混合器)

オペレータグラフ

オペレータグラフ

そんな支援機能ないので

ここではオペレータ関数の
ネストでオペレータグラフを表現します

ピアノ波形を作る

ラの音のオペレータグラフ


    f := float32(440.0)
    OSC := Multiplex(
      G(0.4, GenOscillator(f)),    // base sin wave
      G(0.2, GenOscillator(f*2)),  // 2 times freq
      G(0.1, GenOscillator(f*3)),  // 3 times freq
      G(0.05, GenOscillator(f*4)), // 4 times freq
    )
    

これでOSCは440Hzのピアノの波形を模したオシレータになる

エンベロープの定義


    func GenEnvelope(press *bool, f Oscillator) Oscillator {...}
    

pressはキーの押下状態

入力を決めた形状で

ゲイン変化させて出力

以上の各実装

utils.go

鳴らしてみる1

PLAY

音がチープだった

もう一度ピアノの音解析

寂しいのは高域も低域も足りなさすぎ?

デチューン

ビミョーに周波数の違う音を重ねると
周波数の差のうねりが発生して
コーラスのようなハモりが発生する

コナミがよく使っていたので
コナミ効果とも呼ばれた (80年台)

計算式で見てみる

例えば440Hzと442Hzの
サイン波を足すと・・・?

鳴らしてみる2

PLAY

鍵盤の音のミックス

フツーに考えると、

押した鍵盤ごとに音を再生

サウンドドライバー側で
ミキシング

リソース管理めんどい

なのでMultiplexerで

鍵盤ごとのOscillatorを

あらかじめミキシングしちゃおう!

ピアノモデルの実装

piano.go

これでNewPiano(...).GetOscillator()で

たった一つのOscillatorに
全てが集約されます

あとはこのOscillatorの出力を
延々と再生処理に流すだけ

アプリ実装の解説

OpenALって?

gomobileはOpenALがバンドル

3D空間支援などもあるけれど

オーディオ機能としてはシンプル

基本要素

  • Buffer

  • Source

  • Listener

Buffer

PCMデータのメモリオブジェクト

作成時にサンプルレートを指定

Source

音源の抽象化オブジェクト

空間座標とか持ってる
(今回はListenerと同じ)

Bufferキューを持ち
投入すると順次再生します

Listener

聞き手の抽象化オブジェクト

空間座標とか持ってる
(今回はSouceと同じ)

メモリ管理

OpenALのオブジェクトは
全てAPI利用側で管理

しかもOpenGLと同じですぐロストします

GLと同じlifecycleで管理をしましょう

リアルタイム再生

操作に合わせて適時
再生内容を生成するには?

  • 小さめのBufferを生成してキュー

  • 再生🔊

  • 使い終わったBufferを
    アンキューして破棄します

OpenAL操作実装

audio.go

サイズイベント実装

サイズを保存します

ペイントイベント実装

  • サイズに合わせて鍵盤描画

  • リアルタイム再生処理

タッチイベント実装

ピアノモデルの該当するノートを
オン・オフします

大まかな実装は以上

gomobileでビルド(1)

Android


    go get -d github.com/nobonobo/nobopiano
    gomobile build -target android github.com/nobonobo/nobopiano
    

さっくりnoobopiano.apkができますね?

gomobileでビルド(2)

iOS


    go get -d github.com/nobonobo/nobopiano
    gomobile build -target ios github.com/nobonobo/nobopiano
    

(上記はコード署名発行済みでないとエラー)

iOS用ビルドはちょとコツが要ります。

詳細: iOSアプリをビルドする手順まとめ

動作デモ

子供喜ぶ

アプリ強制終了

子供がっかり

バグの発見

gomobileのソースを読む・・・

管理できないタッチイベントはpanic


    if id == -1 {
      panic("out of touchIDs")
    }
    

これはひどい

これはひどい

ログに出して無視でええやん

トラブルシュート

gomobileの不満

  • アイコンつけたい

  • iOSビルドめんどくさい

解決するツールを作りました

gomobileapp

iOSビルドの調整ができる
アイコンがつけられる

ペイントイベント

サンプルではPaint処理で
Paintイベントを
キューに積む処理を記述

Androidでは必要だった

iOSでは不要だった(CPU負荷が爆発)

buildタグで回避

OpenALのAPIシグニチャー変更

なのでGoChallenge7の回答

golangchallenge/GCSolutions/oct15/
はもうすんなりビルドできない。

GoChallenge7への提出

zipにしてメールという指示

GMailさんに弾かれまくり

(GMailドキュメントには
抵触する理由が見当たらない・・)

コード構成を変えてzipして
やっと送信できた

てな感じで

見た目にあんまり
手間かけれんかった・・・

Dive Into Darkside

ちょっと怖い話

Androidツライ(1)

superpowered.com/latency

ビットレートが低く、バッファが少ないほど低遅延

ハイレゾ機は余計NG

何よりデバイスのバラツキが

Androidツライ(2)

Androidの10ms問題

タッチ遅延もでかい・・・

このプレゼン

実は・・・

Goのコードを動かすのに

GopherJSで・・・ゲフンゲフン

ピアノモデルでベンチ


    $ go get -d -u github.com/nobonobo/nobopiano
    $ go test -cpu 1 -bench . github.com/nobonobo/nobopiano/model
    testing: warning: no tests to run
    PASS
    BenchmarkPianoModel  1000000        1966 ns/op
    ok    github.com/nobonobo/nobopiano/model 2.000s
    $ gopherjs test --bench . github.com/nobonobo/nobopiano/model
    testing: warning: no tests to run
    PASS
    BenchmarkPianoModel   50000       32880 ns/op
    ok    github.com/nobonobo/nobopiano/model 2.878s
    

gopherjsは15倍遅い!

JSは関数の
呼び出しが重い

JS対象の場合
今回の関数ネスト方式は
やめましょう

怖い話

おわり

Conclusion

きょうは

音周りの基礎知識と

gomobileでピアノの実例を解説しました

この資料の情報をアレンジすれば

いろんな楽器アプリへの応用も!

ぜひチャレンジしてみてね!

てんやわんやで

GoChallenge7に応募した結果・・・

落選しました

見た目に変化をつけられなかった
のが理由っぽい

結論

子供は楽しめてるので

ま、いっか!

質疑応答

ご静聴
ありがとう
ございました

おわり