bytebeat まとめ

# 概要
この記事は個人的にbytebeatを調査したもののまとめです。
サンプリングレート8000(8k) bytebeat 0～255(floatbeat 1～-1ではない)前提で書かれています。
[html5bytebeat](https://greggman.com/downloads/examples/html5bytebeat/html5bytebeat.html)前提なので、ワンライナーjavascriptが使える想定です。

# bytebeatとは
コードでビートを作る

ビット演算を多用することからCPUのアセンブラコードに近いです。

bytebeatはどれだけコードを短く音楽を奏でるかに特化しています。デモシーンでのコード圧縮を想定した作りです。
html5bytebeatはワンライナーjavascriptが使えMathモジュールが標準で使えるようになっています。
Math.sinやMath.PIなどがそのままsinやPIが使える。ワンライナーjavascriptなので、複数行書きたい場合はセミコロン『;』が使えません。『,』で複数定義でき、変数の定義や、アロー演算子や関数の定義が出来ます。

※ 注意 bit演算を使うと整数になり、浮動小数点はなくなります

# glitchとは
bytebeatを元にシンセ的な音楽を作りやすい方向に進化したのがglitchになります。

* [グリッチ：バイトビートを超えて](https://medium.com/@naive_sound/glitch-beyond-the-bytebeat-603478a03686)
* https://naivesound.com/glitch/
* https://github.com/naivesound/glitch

本記事ではbytebeatでglitchと同じようにシンセ的な音楽を作る方法について書いていきます。
そもそも、基礎が分かってないとbytebeatのコード圧縮が分からないですし。

# ビット演算

bytebeatで良く使われるビット演算子
ビット演算をした時点で整数になります。小数部切り捨て

## 右シフト演算子 >>

tからbpmを生成するのに使われます。『t>>10』、『t>>11』、『t>>12』あたりが使われます。

## AND演算子 &

主に2つの使われかたをしていてメロディ生成と音色の矩形波生成に使われています。
2の乗数0,1,2,4,8,16...128かどうかで挙動が変わってきます。

###『&2の倍数』
 * メロディ 単一音
 * 音色 矩形波

###『&2の倍数以外』
 * メロディ フラクタルのメロディ
 * 音色 ビット欠け矩形波

## OR演算子 |

『x|0』つまりビット演算的には何も変化がありませんが、ビット演算をした時点で整数になる特徴を利用し、小数部を切り捨てします。≒『floor(x)』floorの代わりに使われますが、xが-だと挙動が異なるので注意

## NOT演算子 ~

フラクタルのメロディで使うとメロディが逆転します。

# 基本波形
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135381/7b7c1946-78eb-8964-bba6-5e12f7a9c546.png)


## 正確な波形

|波形|式|
|:---|:--|
|ノコギリ波|t%256|
|正弦波    |sin(t%256/256*PI*2)*127+128|
|矩形波    |(t%256<128)*255| 
|三角波    |abs(t%256-127)*(255/128)|
|ノイズ    |random()*255|

## ノコギリ波
bytebeatの基本波形はノコギリ波です。
ノコギリ波以外の正確な波形はコードが長いのでbytebeatでは使われません。

出力先が『byte』なので時間変数t(サンプリングレート8kなので1秒間で8000カウント)の出力は256まで行くと0にリセットされることを利用すると

```javascript
t
```

と書くだけでノコギリ波が発生します。が単純に出力する場合には良いのですが、最終アウトプット結果が255以上の数が切り捨てられるということなので、複数ノコギリ波を合成する場合などはt%256としてshaderのfractのような感じで明示的に指定しておいた方が無難です。原因不明のバグに悩まされることになります。最適化の際に取り除けばよいでしょう。
shaderの最終出力結果がsaturateされるのと同じように、途中で加算する場合は、個別にsaturateを掛けておくのと同じです。

## 矩形波

ANDビット演算子の性質『&2のn乗』とすると矩形波が出来ることを利用してbytebeatではコードが短くお手軽なので

```javascript
t&128
```

が良く使われます。ただ、これは正確な矩形波ではありません。片側 矩形波になります。音量的には半分になります。

```javascript
t&64
```

```javascript
t&32
```

```javascript
t&16
```

```javascript
t&8
```

```javascript
t&4
```

```javascript
t&2
```

```javascript
t&1
```

でも矩形波になりますが、音が小さいので使われないです。

なんで

```javascript
t&256
```
が使われないのかというと

```javascript
t%256&256
```

256=0となってしまい音が鳴らなくなってしまうからですね。

```javascript
t&255
```

だと2の倍数ではないので音が出ません

256を255にフィットさせて

```javascript
(t&128)*(255/128)
```

か

```javascript
(t*2&256)*(255/256)
```

で正確な矩形波を出せますがコード量が変わらないので

```javascript
(t%256<128)*255
```

こっちの方が良いでしょう。あと、比率を変えてパルス波を作れるので、シンセ的にもこっちの方がいいです。

```javascript
(t%256<16)*255
```

```javascript
(t%256<200)*255
```

尚、2の乗数でないAND演算をするとビット欠けしたエモイ矩形波が発生します。音色に深みが出るので色々試してみるといいでしょう。

```javascript
t&0xae
```

[&#x1f3b5;音を聞く](https://greggman.com/downloads/examples/html5bytebeat/html5bytebeat.html#t=0&e=0&s=8000&bb=5d000001000600000000000000003a098207c3627361367ffffed6c800)

```javascript
t&0xef
```
[&#x1f3b5;音を聞く](https://greggman.com/downloads/examples/html5bytebeat/html5bytebeat.html#t=0&e=0&s=8000&bb=5d000001000600000000000000003a098207c38367f41a9ffffecd8000)

など
尚上位ビットを削りすぎると音が鳴らなくなるので注意が必要です。

```javascript
t&3
```
[&#x1f3b5;音を聞く](https://greggman.com/downloads/examples/html5bytebeat/html5bytebeat.html#t=0&e=0&s=8000&bb=5d000001000300000000000000003a0982707ffffffff8000000)

とかほとんど音が鳴らない

## 三角波

簡易版

```javascript
abs(t%255-127)*2
```

absを使う場合、0が中心の場合、範囲が奇数じゃないと行けないので256-1で
1ビット欠けてますね。音的にはあまり変わりません。

```javascript
abs(t%256-127)*(255/128)
```

これも正確にやるなら256を255に丸めてあげる必要があります。

## 正弦波
音叉、基準音、時報の音です。単体ではつまらない音なので単体ではあまり使われません。
基本的に正弦波同士を掛け合わせ、FM変調して使います。

```javascript
sin(t*(440/8000*256)%256/256*PI*2)*127+128
```

## ノイズ

```javascript
random()*255
```

上記の標準的なものから、tを高周波数にして適当にビット演算してノイズっぽくしたもの
shaderのノイズと同じようにsinでカオスを作って疑似ノイズ
ビット演算が使えるからxorshiftでノイズを作るなどがあります。

音楽的にノイズとして聞こえれば何でもよいかと

# メロディを奏でる
## 基準音 440Hz ラを鳴らす
srは8000Hzでbtye(256)
(440/8000*256)をtに掛ければいい

ノコギリ波 t*(440/8000*256)%256
正弦波     sin(t*(440/8000*256)%256/256*PI*2)*127+127
矩形波     (t*(440/8000*256)%256<128)*255
三角波     abs(t*(440/8000*256)%255-127)*2

## テンポ(bpm)
サンプリングレートが8kなのでtは1秒間に8000カウントアップされます。
なので、tは1分間に60 x 8000 = 480000 bpm
ということになります。

```javascript
abs((t*440*pow(2,[3,5,7,8,10,12,14,15][t%8]/12)/8000*256)%256-127)*(255/128) 
```

このまま鳴らしたらノイズにしかならないので

ビット演算子を使って

* 480000 >> 11 = 234 bpm 

```javascript
t>>11
```

* 480000 >> 12 = 117 bpm 

```javascript
t>>12
```

音楽的には主に上の2つが使われます。8分音符や16分音符を使いたい場合はビットシフト>>を減らせばよし

ビット演算を使った時点で整数に丸められるのでシンプルです。

付点、連符の場合はビットシフトが使えないので注意!!

細かいテンポを指定したい場合は 200 bpmにしたい場合は

```javascript
t/(480000 / 200)|0
```

```javascript
abs((t*440*pow(2,[3,5,7,8,10,12,14,15][(t/(480000 / 200)|0)%8]/12)/8000*256)%256-127)*(255/128)
```

[&#x1f3b5;音を聞く](https://greggman.com/downloads/examples/html5bytebeat/html5bytebeat.html#t=0&e=0&s=8000&bb=5d0000010061000000000000000030988ac8b7849e80658a1a3d20fb8c7d3d22f76dfa3c3138718eb516bdf7936dc3e0eb6260229aae4be14fc863d4c465acffd3e1d39129f05dcff49c05aa9058e9d6106ae69b084d9f77eb5cd37019baed5da97fae413b3ffff3ecd000)

## ドレミファソラシドを鳴らす

```javascript
abs((t*440*pow(2,[3,5,7,8,10,12,14,15][(t>>12)%8]/12)/8000*256)%256-127)*(255/128) 
```

[&#x1f3b5;音を聞く](https://greggman.com/downloads/examples/html5bytebeat/html5bytebeat.html#t=0&e=0&s=8000&bb=5d0000010052000000000000000030988ac8b7849e80658a1a3d20fb8c7d3d22f76dfa3c3138718eb516bdf7936dc3e0eb6260229aae7a10345d3bb8a4f68e4f8ef6dd71bb2dfd7e6bba893f214cd594be7a7152d18b81ea4b14e0a4fe714540)

## フラクタルで鳴らす

詳細はこちらを参考に

* https://miujun.hatenadiary.org/entry/20111031/p1

上記を踏まえ、&演算子を使ってフラクタル(シェルピンスキー)を使って音を鳴らしてみましょう
※ AND演算子の特徴として2の乗数は1音しか鳴りません

```javascript
((t >> 10) & 42) * t % 128
```

デフォルトのサンプル曲がまさにそうで、42メロディと言われています。

```javascript
((t >> 10) & 42) * t % 128
```

```javascript
((t >> 10) & 3) * t % 128
```

3,5,6,7,9,10,11,12,13,14,15,17,18,19,20....42が限界
色々試してみましょう。

フラクタルを使っていると、だいたい同じような感じになってしまいます
ビット演算~notを使うと

```javascript
(~(t >> 11) & 5) * t % 128
```

メロディを逆転できます。

このフラクタルを色々加工して音楽を作っていきます。

## 同時に複数音を出す

単純に足せばよいです。
%256の付け忘れに注意

# エフェクト
## ディレイ

```javascript
(s = t => (~(t >> 10) & 5 ) * t & 128),s(t)+s(t-100)
```

[&#x1f3b5;音を聞く](https://greggman.com/downloads/examples/html5bytebeat/html5bytebeat.html#t=0&e=0&s=8000&bb=5d00000100340000000000000000141cc003d01db32d61e1c98918faf02a3c59a1af8c4112bcd7c9d814450d29fc73d4fd97f7ce9a5baba339b885ed34994a524339fafca000)

## ユニゾン(デチューン)

```javascript
 (s = t => (~(t >> 10) & 5 ) * t & 128),s(t)+s(t*(1+1e-6))
```

[&#x1f3b5;音を聞く](https://greggman.com/downloads/examples/html5bytebeat/html5bytebeat.html#t=0&e=0&s=8000&bb=5d00000100390000000000000000141cc003d01db32d61e1c98918faf02a3c59a1af8c4112bcd7c9d814450d29fc73d4fd97f7ce9a5baba339aee88682dab3a81a4e810b9ab7fff15c6800)

## フィードバック

```javascript
(s = t => (~(t >> 10) & 5 ) * t &0xae ),s(t)+s(s(t))
```

[&#x1f3b5;音を聞く](https://greggman.com/downloads/examples/html5bytebeat/html5bytebeat.html#t=0&e=0&s=8000&bb=5d00000100340000000000000000141cc003d01db32d61e1c98918faf02a3c59a1af8c4112bcd7c9d8138541f1ad14f73384f91f3d1d26996dc8da32e0dd9abcfffdda2400)

## 簡易オーバードライブ

```javascript
(s = t => (~(t>> 10) & 5 ) * t % 256 ),min(255,max(0,(s(t)-128)*2+128))
```

[&#x1f3b5;音を聞く](https://greggman.com/downloads/examples/html5bytebeat/html5bytebeat.html#t=0&e=0&s=8000&bb=5d00000100470000000000000000141cc003d01db32d61e1c98918f97878be54db9197308daad8b806dbeff2f05ad58e2409f1ac219aef02f9c83c5239e00cbb0ba4b62b370f357209e8c4bcad5ca4acdc292177ff019e0000)

# FM音源のフィードバック

## fm音源feedback

* 1:1 ノコギリ

```javascript
tt=t%256/256*PI*2,sin(tt+(sin(tt*2+sin(tt*4+sin(tt*8+sin(tt*16))))))*127+128
```

再帰対応版

```javascript
tt=t%256/256*PI*2,fb=((f=(n,t,r)=>sin(t+(n>1?f(n-1,t*r,r):0)))=>f)(),fb(127,tt,1)*127+128
```

* 1:1 矩形波

```javascript
tt=t%256/256*PI*2,sin(tt+(sin(tt+sin(tt+sin(tt+sin(tt))))))*127+128
```

再帰対応版

```javascript
tt=t%256/256*PI*2,fb=((f=(n,t,r)=>sin(t+(n>1?f(n-1,t*r,r):0)))=>f)(),fb(127,tt,2)*127+128
```

127回feedbackしても綺麗な波形にならない。

アロー演算子で再帰はこちらを参考に、肝はデフォルトパラメーター

* https://qiita.com/yohhoy/items/5f1222fb1ee2457688fc
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters

# PCMでスネア再生

ドラムキットはPCMで音色が欲しくなるのでPCMでドラムを再生できるようにする。
glitchはtr808が用意されている。さすが。

8bit waveデータを16進数で文字列化する

bytebeatはWAVのフォーマット準拠なので文字列化したものを流すだけです。
8bitだったら0～256、16bitだったら-32767～32768
デコーダーは簡単

```javascript
'7e69c5882d6ca4ad6035a6877064769b5871718b67739d7c786f97726b66937b707f848d788173737b8169807d81708184867775807d797c7d7a7a7e7c767a7f7f837e7a807d797e817e807c7f817c7d7d7d7f807e7c7d7d7c7d7c7e7d7d7d7c7d7d7d7c7d7d7e7d7e7c7e7d7e7d7d7e7d7e7c7e7e7c7e7f7d7c7d7c7d7e7e7e7e7d7d7d7c7d7e7e7d7d7d7c7c7d7e7d7d7e7d7e7e7c7d7e7c7d7d7e7d7c7c7c7e7c7e7d7d7d7d7c7d7d7d7c7e7c7d7e7d7c7e7d7e7e7c7e7d7c7e7e7d7e'.match(/.{2}/g).map(x=>'0x'+x|0)[(t/12|0)%200]||128
```

[&#x1f3b5;音を聞く](https://tinyurl.com/t3vftnk)

WAVファイルからデータを抜き出して16進数文字列化するのがめんどく
とりあえず、javascriptでwaveファイルをWebAudioに読み込んで、そのメモリ内容を16進数文字列に変換した。

WebAudioで読み込むとメモリに44.1K固定で展開されるらしく適当にデータを間引いている
あと、Array.fromでarraybuffer(float型付配列)を通常のarrayに変換している。こうしないと後続のmapの結果もfloatに強制され最終結果がおかしくなる。特にmapで文字列に変換するとおかしな結果を招く。webAudioあるある。

```
Array.from(channelLs).filter((x,i)=>i%(441000/8000|0) ===0 ).map(x=>parseInt(((x*128+127)|0)).toString(16)).join('')
```

webAudio経由ではなく、直接WAVファイルを解析した方がいいかも

全文

```javasacript
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title></title>
<script>
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();
var xhr = new XMLHttpRequest();
var url = 'http://localhost/pcm/8bitdrum.wav';
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
  if (xhr.status === 200) {
    var arrayBuffer = xhr.response;
    if (arrayBuffer instanceof ArrayBuffer) {
      var successCallback = function(audioBuffer) {
          var channelLs = new Float32Array(audioBuffer.length);
          var channelRs = new Float32Array(audioBuffer.length);
          if (audioBuffer.numberOfChannels > 1) {
            channelLs.set(audioBuffer.getChannelData(0));
            channelRs.set(audioBuffer.getChannelData(1));
          } else if (audioBuffer.numberOfChannels > 0) {
            channelLs.set(audioBuffer.getChannelData(0));
          } else {
            window.alert('The number of channels is invalid.');
            return;
          }
          console.log(
            Array.from(channelLs).filter((x,i)=>i%(441000/8000|0) ===0 ).map(x=>parseInt(((x*128+127)|0)).toString(16)).join('')
          );
      };
      var errorCallback = function(error) {};
      context.decodeAudioData(arrayBuffer, successCallback, errorCallback);
    }
  }
};
xhr.open('GET', url, true);
xhr.send(null);
</script>
</head>
<body></body>
</html>
```


# 欲しいけどまだ作っていないもの
だいたいglitchの方では実装されている

## FM音源
 基本的に正弦波の出力を正弦波の入力に入れる重ね合わせの連鎖、あとは、どう加算するか
### エンベローブ
 音量変化
 基本三角波とかと同じだが、比率とかあるので、参考演算子で実装かな

## ローパスフィルタ
 生シンセデータを鳴らしていると高周波成分が気になるのでローパスフィルタを掛けたい
 画像処理のブラ―と同じように前後のデータから平均値をフィルターを掛ければいいが、計算式の実装がちょい面倒

## リバーブ
 原理的はフィードバックとディレイと同じだけどPCMと同じでIRデータを持ってくるのが面倒

## オーバードライブ
 クリッピングだけではなく歪みも実装
 これもローパスフィルターと同様

# bytebeat的コード圧縮解説まとめ

## ビット演算 ビットシフト>> でbpmを作る

右ビットシフトは2の乗数で割る

具体的に以下3つのコードが良く使われる

|bpm|式|
|:---|:--|
|468|t>>10|
|234|t>>11|
|117|t>>12| 

任意のbpmは割ればいいがコードが長くなるのであまり使われない。
例えばbpm 200であれば『t/(480000/200)|0』=『t/2400|0』

## メロディはANDビット演算のフラクタルで作る

2の乗数ではないANDビット演算はフラクタルなメロディを作るのでこれを加工してメロディを作る &3,&5,....,&42など

## 音色は、デフォルトのノコギリ波を使う
つまり『t』、たまに2のn乗のANDビット演算子を使い矩形波を使う『t&128』

代わりに、『t&0xae』、『t&0xef』とか使っても良いでしょう。ビット欠けの矩形波は他のシンセサイザーにはない深みのある音色になります。

## まとめると

```javascript
(t >> 10) & 5 ) * t
```

これだけでメロディを作ることができる。最少構成

# 参考
* https://www.reddit.com/r/bytebeat/