【Go】打ち込みでスマホの着メロを作ろう【3和音】

昭和エンジニアの四谷です。

自作プログラムと打ち込み譜面で、スマホの着メロを作りたいと思います！
タイトルに「3和音」と書きましたが、「携帯 3和音」でググると、出るわ出るわ、なつかしいiモード。イメージ的にはあんな感じ。ビープ音による3和音に挑戦してみます。

こちらを参考にさせていただきました。

https://news.mynavi.jp/techplus/article/gogogo-18/


## おおまかな手順

独自の定数表現で楽譜を打ち込んで音楽を作成し、できた音源をスマホ(iPhone)に着信音として登録します。

プログラムで音符打ち込み
↓
実行
↓
wavファイル生成
↓
ファイル形式を変換しスマホに転送
↓
着信音に登録
↓
完成！

---

## 打ち込む音の定義

高さ/強さ/長さの構造体とします。
音楽についての基本知識的なものは[楽典](https://xn--i6q789c.com/)をご参考ください。

https://xn--i6q789c.com/

### 高さ
ピアノと同じ88鍵の周波数を以下サイトを参考にしました。

https://tomari.org/main/java/oto.html

国際式表記では`ド`の音を`C`と表現しますが、今回は私が打ち込みやすくすることを目的として、独自の表記法を作りました。

#### 高さの表記法(独自)

国際式表記の音階/オクターブ/シャープ(フラット)の表現を転置し、自分が入力しやすいフォーマットに変えました。

ピアノの2オクターブ目の`ド`の`シャープ`(C2#)を例にして、独自表現にしてみましょう。

- 独自表現での音階番号
    音階番号(独自)|国際式|音
    :--|:--|:--
    1|C|ド
    2|D|レ
    3|E|ミ
    4|F|ファ
    5|G|ソ
    6|A|ラ
    7|B|シ
- シャープ(フラット)
    - シャープが付くとき末尾に`s`を付与
    - フラット(♭=半音下げる)は使用しません。

これを組み合わせ↓のような表現になります。
 
k|オクターブ(0〜8)|音階番号(1〜7)|シャープ(s)または無記
 :--|:--|:--|:--
k|2|1|s

つまり`C2#`の定数は `k21s` となります。

### 強さ

定数|強さ|意味|楽譜記号
:--|:--|:--|--
F|フォルテ|強く|![f](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/3f9da658-1def-c567-b096-9f5e5099070d.png)
MF|メゾフォルテ|やや強く|![mf](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/d0073d6e-63f0-c8b8-3deb-f704e0b8bbaa.png)
N|(普通)|-|-
MP|メゾピアノ|やや弱く|![mp](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/73bf2bf5-2689-0bd0-5570-6705c2d82ff0.png)
P|ピアノ|弱く|![p](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/173c9629-4e0f-4ae2-4f1d-017041d48b3c.png)
M|(無音)|-|-

- ピアノの場合、和音のうち特定の音だけを強く弾いたり、リズムのアクセントをつけたりすることで、表現が豊かになります。
- ビープ音では、すべてが強すぎると音が割れるので、メロディラインとアクセントは強めに。ベースは弱めでクライマックスはやや強めに、といった表現をしたいと思います。
- 休符を定義しない代わりに、`M`(無音)を定義しました。

### 長さ

定数|長さ|音符　
:--|:--|:--
L1|全音符|![l1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/713fbd53-7261-9e2c-4933-12a50fc37056.png)
L2|2分音符|![l2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/8097e974-fbc5-0f50-ff4a-27cbc22e7c66.png)
L2H|符点2分音符|![l2h](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/8f78ef22-4bd1-9b35-c2f0-64f56df2cee5.png)
L4|4分音符|![l4](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/be976fe9-22d1-c9a9-f500-6883c87f2d65.png)
L4H|符点4分音符|![l4h](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/c5b65d47-06c3-a309-e84e-527b59963796.png)
L8|8分音符|![l8](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/49755a7a-a88b-4cad-e2e0-b45e73950897.png)
L8H|符点8分音符|![l8h](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/2e06466d-0d2d-6449-d1a8-b72f17cf83b5.png)
L16|16分音符|![l16](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/5034a790-f44f-2124-4720-f4701255b208.png)
L16H|符点16分音符|![l16h](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/3df320a7-0446-bf79-4d4d-8ea24eff7c24.png)
L32|32分音符|![l32](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/8f29c218-7c75-6097-5a02-43d05447c36d.png)

---

## サンプルコード
- 諸々の定義をしおえたところで、サンプルコードを実行します。
- wavファイルを生成することができるようになりました。
- 「プー」と音がなるだけのものです。

```go
package main

import (
	"math"
	"os"

	gowav "github.com/youpy/go-wav"
)

type (
	Score struct {
		K float64 // 鍵
		V float64 // 強さ
		L float64 // 音符
	}
)

// ピアノ88鍵盤
const (
	k06  float64 = 27.500
	k06s float64 = 29.135
	k07  float64 = 30.868
	k11  float64 = 32.703
	k11s float64 = 34.648
	k12  float64 = 36.708
	k12s float64 = 38.891
	k13  float64 = 41.203
	k14  float64 = 43.654
	k14s float64 = 46.249
	k15  float64 = 48.999
	k15s float64 = 51.913
	k16  float64 = 55.000
	k16s float64 = 58.270
	k17  float64 = 61.735
	k21  float64 = 65.406
	k21s float64 = 69.296
	k22  float64 = 73.416
	k22s float64 = 77.782
	k23  float64 = 82.407
	k24  float64 = 87.307
	k24s float64 = 92.499
	k25  float64 = 97.999
	k25s float64 = 103.826
	k26  float64 = 110.000
	k26s float64 = 116.541
	k27  float64 = 123.471
	k31  float64 = 130.813
	k31s float64 = 138.591
	k32  float64 = 146.832
	k32s float64 = 155.563
	k33  float64 = 164.814
	k34  float64 = 174.614
	k34s float64 = 184.997
	k35  float64 = 195.998
	k35s float64 = 207.652
	k36  float64 = 220.000
	k36s float64 = 233.082
	k37  float64 = 246.942
	k41  float64 = 261.626
	k41s float64 = 277.183
	k42  float64 = 293.665
	k42s float64 = 311.127
	k43  float64 = 329.628
	k44  float64 = 349.228
	k44s float64 = 369.994
	k45  float64 = 391.995
	k45s float64 = 415.305
	k46  float64 = 440.000
	k46s float64 = 466.164
	k47  float64 = 493.883
	k51  float64 = 523.251
	k51s float64 = 554.365
	k52  float64 = 587.330
	k52s float64 = 622.254
	k53  float64 = 659.255
	k54  float64 = 698.456
	k54s float64 = 739.989
	k55  float64 = 783.991
	k55s float64 = 830.609
	k56  float64 = 880.000
	k56s float64 = 932.328
	k57  float64 = 987.767
	k61  float64 = 1046.502
	k61s float64 = 1108.731
	k62  float64 = 1174.659
	k62s float64 = 1244.508
	k63  float64 = 1318.510
	k64  float64 = 1396.913
	k64s float64 = 1479.978
	k65  float64 = 1567.982
	k65s float64 = 1661.219
	k66  float64 = 1760.000
	k66s float64 = 1864.655
	k67  float64 = 1975.533
	k71  float64 = 2093.005
	k71s float64 = 2217.461
	k72  float64 = 2349.318
	k72s float64 = 2489.016
	k73  float64 = 2637.020
	k74  float64 = 2793.826
	k74s float64 = 2959.955
	k75  float64 = 3135.963
	k75s float64 = 3322.438
	k76  float64 = 3520.000
	k76s float64 = 3729.310
	k77  float64 = 3951.066
	k81  float64 = 4186.009
)

// 音の強さ
const (
	F  = 0.3   // フォルテ
	MF = F / 2 // メゾフォルテ
	N  = F / 5 // 普通
	MP = N / 2 // メゾピアノ
	P  = N / 5 // ピアノ
	M  = 0.000 // 無音
)

// サンプリングレート
const Rate = 48000

// テンポ
const Tempo = 180

// 音符の種類
const (
	L4   = float64(Rate * 60 / Tempo) // 4分音符
	L1   = L2 * 2                     // 全音符
	L2   = L4 * 2                     // 2分音符
	L2H  = L2 + L4                    // 符点2分音符
	L8   = L4 / 2                     // 8分音符
	L4H  = L4 + L8                    // 符点4分音符
	L16  = L8 / 2                     // 16分音符
	L8H  = L8 + L16                   // 符点8分音符
	L32  = L16 / 2                    // 32分音符
	L16H = L16 + L32                  // 符点16分音符
)
const (
	MAX = 60000
	MIN = -10000
)

func main() {
	sin1 := Sin([]Score{{k45s, F, L1}})
	sin2 := Sin([]Score{{k33, F, L1}})
	Mix(&sin1, &sin2)
	Write(sin1)
}
func Max(a, b int) int {
	if a+b > MAX {
		return MAX
	} else {
		return a + b
	}
}
func Min(a, b int) int {
	if a+b < MIN {
		return MIN
	} else {
		return a + b
	}
}
func Sin(score []Score) []gowav.Sample {
	var samples []gowav.Sample
	for _, s := range score {
		for i := 0; i < int(s.L); i++ {
			si := math.Sin((float64(i) / Rate) * s.K * 2.0 * math.Pi)
			gw := gowav.Sample{}
			gw.Values[0] = int(si * 0x7fff * s.V)
			samples = append(samples, gw)
		}
	}
	return samples
}
func Mix(wav1, wav2 *[]gowav.Sample) {
	for i, _ := range *wav1 {
		if len(*wav2) < i {
			break
		}
		if (*wav1)[i].Values[0] < 0 {
			(*wav1)[i].Values[0] = Min((*wav1)[i].Values[0], (*wav2)[i].Values[0])
		} else {
			(*wav1)[i].Values[0] = Max((*wav1)[i].Values[0], (*wav2)[i].Values[0])
		}
	}
}
func Write(samples []gowav.Sample) error {
	fp, err := os.OpenFile("a.wav", os.O_TRUNC|os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		return err
	}
	defer fp.Close()
	writer := gowav.NewWriter(fp, uint32(len(samples)), 2, Rate, 16)
	writer.WriteSamples(samples)
	return nil
}
```

### できたwavファイル

<audio controls src="https://github.com/aucfan-yotsuya/compose/blob/main/a.wav?raw=true"><a href="https://github.com/aucfan-yotsuya/compose/blob/main/a.wav?raw=true">Downloadして聴く</a>
</audio>

### wavファイルの波形
- ソフトの都合なのかギザってますが、正弦波になっております。

![正弦波](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/ae42c5d1-841c-cee6-78c4-b788e9ab4322.png)

---

## 和音
2つの波形値を加算することで実現します。
また、合成する際に音がつぶれないようにするため数値の上限、下限を制限します。
3和音(A,B,C)を合成する場合は、`A=A+B`→`A=A+C`のステップを踏むことになります。

```go
// 正弦波サンプリングデータ2つを渡して加算処理
func Mix(wav1, wav2 *[]gowav.Sample) {
	for i, _ := range *wav1 {
		if len(*wav2) < i {
			break
		}
		if (*wav1)[i].Values[0] < 0 {
			(*wav1)[i].Values[0] = Min((*wav1)[i].Values[0], (*wav2)[i].Values[0])
		} else {
			(*wav1)[i].Values[0] = Max((*wav1)[i].Values[0], (*wav2)[i].Values[0])
		}
	}
}
```

---

## ハノンを打ち込んでみよう

ハノンとは、ピアニストにとって、指の筋トレのようなものだと思ってください。
内容は機械的ですが、持久力がついたり、粒の揃った音を奏でられるようになるので、世界中で愛用されています。
ハノンの最初、第1番の楽譜を、ホ長調(基準の音が`ミ`)で、`タンタタタン タンタタタン`とリズムをつけたカスタム練習です。
普段、私が練習してるので、なんとなく！

![hanon1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/8df1123d-044e-7d07-1677-bf769499c7dc.png)

1拍目と3拍目を「強/弱/中/弱」のようなアクセントも付けたいと思います。
それを1オクターブ(12音差)で両手で弾くイメージでL/Rの音をそれぞれ定義。(2和音ですね)
いきなりコードに落とし込むのは大変なので、打ち込み元データはGoogleスプレッドシートで作成しました。(だって人間ですもの)

### 打ち込み元データ(イメージ)

![打ち込み元データ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/d4041fd6-01eb-6df5-14c7-f4adeac0d0c3.png)

### 打ち込み後コード

```go
// ハノン(高音)
func Hanon() []Score {
	return []Score{
		{k43, F, L8}, {k45s, N, L16}, {k46, N, L16}, {k47, N, L8},
		{k51s, MF, L8}, {k47, N, L16}, {k46, N, L16}, {k45s, N, L8},
		{k44s, F, L8}, {k46, N, L16}, {k47, N, L16}, {k51s, N, L8},
		{k52s, MF, L8}, {k51s, N, L16}, {k47, N, L16}, {k46, N, L8},
		{k45s, F, L8}, {k47, N, L16}, {k51s, N, L16}, {k52s, N, L8},
		{k53, MF, L8}, {k52s, N, L16}, {k51s, N, L16}, {k47, N, L8},
		{k46, F, L8}, {k51s, N, L16}, {k52s, N, L16}, {k53, N, L8},
		{k54s, MF, L8}, {k53, N, L16}, {k52s, N, L16}, {k51s, N, L8},
		{k47, F, L8}, {k52s, N, L16}, {k53, N, L16}, {k54s, N, L8},
		{k55s, MF, L8}, {k54s, N, L16}, {k53, N, L16}, {k52s, N, L8},
		{k51s, F, L8}, {k53, N, L16}, {k54s, N, L16}, {k55s, N, L8},
		{k56, MF, L8}, {k55s, N, L16}, {k54s, N, L16}, {k53, N, L8},
		{k52s, F, L8}, {k54s, N, L16}, {k55s, N, L16}, {k56, N, L8},
		{k57, MF, L8}, {k56, N, L16}, {k55s, N, L16}, {k54s, N, L8},
		{k53, F, L2},
	}
}

// ハノン(低音)
func HanonL() []Score {
	return []Score{
		{k33, F, L8}, {k35s, N, L16}, {k36, N, L16}, {k37, N, L8},
		{k41s, MF, L8}, {k37, N, L16}, {k36, N, L16}, {k35s, N, L8},
		{k34s, F, L8}, {k36, N, L16}, {k37, N, L16}, {k41s, N, L8},
		{k42s, MF, L8}, {k41s, N, L16}, {k37, N, L16}, {k36, N, L8},
		{k35s, F, L8}, {k37, N, L16}, {k41s, N, L16}, {k42s, N, L8},
		{k43, MF, L8}, {k42s, N, L16}, {k41s, N, L16}, {k37, N, L8},
		{k36, F, L8}, {k41s, N, L16}, {k42s, N, L16}, {k43, N, L8},
		{k44s, MF, L8}, {k43, N, L16}, {k42s, N, L16}, {k41s, N, L8},
		{k37, F, L8}, {k42s, N, L16}, {k43, N, L16}, {k44s, N, L8},
		{k45s, MF, L8}, {k44s, N, L16}, {k43, N, L16}, {k42s, N, L8},
		{k41s, F, L8}, {k43, N, L16}, {k44s, N, L16}, {k45s, N, L8},
		{k46, MF, L8}, {k45s, N, L16}, {k44s, N, L16}, {k43, N, L8},
		{k42s, F, L8}, {k44s, N, L16}, {k45s, N, L16}, {k46, N, L8},
		{k47, MF, L8}, {k46, N, L16}, {k45s, N, L16}, {k44s, N, L8},
		{k43, F, L2},
	}
}
```

### できたwavファイル

<audio controls src="https://github.com/aucfan-yotsuya/adventcalendar2022/blob/main/hanon.wav?raw=true"><a href="https://github.com/aucfan-yotsuya/adventcalendar2022/blob/main/hanon.wav?raw=true">Downloadして聴く</a>
</audio>

---

## いよいよ3和音「ジングル・ベル」を打ち込んでみよう
「ジングル・ベル」を3和音でアレンジしました。

![ジングル・ベル](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/2a1b3f0c-615e-715d-98c7-635828cd8adc.png)

### 打ち込み後コード

高/中/低に分割されており、いずれも全体の長さは同じであることがポイントです。
たった4小節と短いものですが、外声(多声部楽曲の一番上及び下のパートのこと)のアクセントに気を使いながら、より立体的な仕上げを目指しました。

```go
func Xmas() []Score {
	return []Score{
		{k51, F, L8}, {k51, F, L8}, {k51, F, L4}, {k51, MF, L8},
		{k51, MF, L8}, {k51, MF, L4}, {k51, MF, L8}, {k52s, MF, L8},
		{k45s, MF, L8H}, {k46s, N, L16}, {k51, MF, L2}, {k51s, MF, L8},
		{k51s, MF, L8}, {k51s, MF, L8}, {k51s, MF, L8}, {k51s, MF, L8},
		{k51, MF, L8}, {k51, MF, L8}, {k51, MF, L8}, {k52s, F, L8},
		{k52s, F, L8}, {k51s, F, L8}, {k46s, F, L8}, {k45s, F, L2},
	}
}
func XmasM() []Score {
	return []Score{
		{k45s, MF, L8}, {k45s, MF, L8}, {k45, N, L4}, {k44, MF, L8},
		{k44, MF, L8}, {k43, N, L4}, {k42s, MF, L8}, {k45s, N, L8},
		{k41, MF, L8H}, {k42s, N, L16}, {k45s, MF, L8}, {k45s, MF, L8},
		{k45, MF, L8}, {k44s, MF, L8}, {k44s, MF, L8}, {k44s, MF, L8},
		{k44, MF, L8}, {k44, MF, L8}, {k43, N, L8}, {k42s, N, L8},
		{k42s, N, L8}, {k42s, MF, L8}, {k42s, F, L8}, {k42s, MF, L8},
		{k42s, F, L8}, {k41s, MF, L8}, {k41, MF, L2},
	}
}
func XmasL() []Score {
	return []Score{
		{k35s, MF, L4}, {k35, MF, L4}, {k34, N, L4}, {k33, N, L4},
		{k35s, N, L4}, {k32s, N, L4}, {k35s, MF, L8}, {k35s, MF, L8},
		{k36s, MF, L8}, {k41, MF, L8}, {k36s, MF, L4}, {k32s, N, L4},
		{k35s, MF, L4}, {k31s, N, L4}, {k32s, N, L8}, {k32s, N, L8},
		{k34, N, L8}, {k35, N, L8}, {k35s, MF, L2},
	}
}
```

## できたwavファイル

<audio controls src="https://github.com/aucfan-yotsuya/adventcalendar2022/blob/main/xmas.wav?raw=true"><a href="https://github.com/aucfan-yotsuya/adventcalendar2022/blob/main/xmas.wav?raw=true">Downloadして聴く</a>
</audio>

---

## 着メロを作るぞ！

曲は創聖のアクエリオンにしました。なんとなく！
爽快なリズム感がありますね。何年経ってもいいアニソンだと思います。
それにしても待てよ...打ち込むにしても、ちとこれは難しい。

サビのコード進行はこういう感じでしょうか。

https://rechord.cc/3cAQz1H6fhU

なんとなくイメージが固まったところで、まずは、鬼のようにスプシに書きました。

![アクエリスプシ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/9ebbe05f-9406-2ed3-26ae-d409ffba7608.png)
(つらい!! つらすぎる!!)

そして、できた3和音の楽譜がこちら。

![アクエリオン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/5a576961-2377-6490-b66a-83c5d410b56a.png)


`タッタッタッ` と休符を活用しながら、リズム感を表現。

### 聴いてみよう

それではお聴きください、こちら！

<audio controls src="https://github.com/aucfan-yotsuya/adventcalendar2022/blob/main/aquarion.wav?raw=true"><a href="https://github.com/aucfan-yotsuya/adventcalendar2022/blob/main/aquarion.wav?raw=true">Downloadして聴く</a>
</audio>

---

## 着メロに登録

wavファイルをaac形式に変換し、.m4r拡張子に変更してiPhoneに転送。
登録されたのがこちら。

![chakumero.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62318/a2226e0e-9fa2-68c2-ae2d-02c008ac6695.jpeg)

---

## スマホに電話をかけてみよう

実際に電話を発信して鳴らしてみました。

<audio controls src="https://github.com/aucfan-yotsuya/adventcalendar2022/blob/main/call.m4a?raw=true"><a href="https://github.com/aucfan-yotsuya/adventcalendar2022/blob/main/call.m4a?raw=true">Downloadして聴く</a>
</audio>

　
 　
　
　
　
　
　
　
　
　
わい、感動や...
 
  
  
  
  
  
 
---

## 最後に
アドベントカレンダー担当の日が迫る中、ようやく重い腰をあげたのが3日前ｗ
記事を書くにあたり、曲をアレンジしたり、後付で楽譜をこしらえたりするのに苦労しましたが、楽めました。
ぜひ皆さんもチャレンジしてみてください。

---

## ソース

https://github.com/aucfan-yotsuya/adventcalendar2022