TouchDesignerでchiptune

# はじめに

先日のTouchDesignerもくもく会にて発表したものです。
TouchDesignerで音生成しているのってあんまりみたことないので、軽く解説してみます。

インタクティブミュージックの入門としても良いかも。

動画：
[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/gHrNLF7SrGM/0.jpg)](http://www.youtube.com/watch?v=gHrNLF7SrGM)

チップチューンっぽい音をTouchDesignerで鳴らしてみたいと思って作ってみたもの。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/bc3bc522-1eac-1eb9-e0e6-eb4e49e9803c.png)

# ダウンロード

発音部分だけ切り出した
toxファイルはこちら
https://github.com/tatmos/chiptuneRnd

# 説明

ほとんどpythonで書いています。

3つの音源を AudioOscillatorで用意し
メロ、ノイズ、バスドラ
という感じで鳴らし分けています。

これくらい鳴っていると音楽っぽくなるかなと。


# 8ステップのゲートシーケンサー
それぞれ8stepの発音テーブルを持っていて、1だと発音します。

``` .py
melo = {
    0: 1,
    1: 0,
    2: 0,
    3: 0,
    4: 1,
    5: 0,
    6: 0,
    7: 0,
}
noise = {
    0: 1,
    1: 1,
    2: 0,
    3: 1,
    4: 1,
    5: 1,
    6: 0,
    7: 1,
}
bd = {
    0: 1,
    1: 0,
    2: 0,
    3: 0,
    4: 1,
    5: 0,
    6: 0,
    7: 0,
}
```

メロディーはscaleをもっていて、音楽的に外れない感じの音になるようにしています。

# メトロノームのようなもの
LFOからくる信号で
onOffToOn
になった時にステップが進むようになっています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/3f6c0cdc-7017-91d1-5541-6c0dcb0fda66.png)

# 音程を決めるもの

周波数指定なので、MIDIノート的に指定できるように少し変えてあります。

```
        op('audioosci1').par.frequency = 442 * pow(2,(randNum-69)/12.0)
```

# 音量変化なもの
音量をランダムにつけています。

```
op('audioosci1').par.amp = 0.2*random.randint(0,4);
```

## ピッチエンベロープ

whileOnのところでエンベロープ的なことも　このfrequency - 8 とかを変更するとピッチの変化が変わります

```
def whileOn(channel, sampleIndex, val, prev):
#   print("On")
#   if op('audioosci1').par.amp < 1:
#       op('audioosci1').par.amp = op('audioosci1').par.amp + 0.1
    op('audioosci3').par.frequency = op('audioosci3').par.frequency -8;
    return
```

## エンベロープリリース

```
def whileOff(channel, sampleIndex, val, prev):
#   print("whileOff")
    if op('audioosci1').par.amp > 0:
        op('audioosci1').par.amp = op('audioosci1').par.amp - 2.4   
        op('audioosci1').par.frequency = op('audioosci1').par.frequency -60; 
```

徐々に音が小さくなるような処理や
バスドラムは音程が下がっていくようなこともしています。

ノイズの変化量とかを変えると面白いです。（ハイハットやシンバルといった感じに変化）


# ボタンを押すと

それぞれの発音テーブルをランダムにします。

``` .py
def onOnToOff(channel, sampleIndex, val, prev):
	mod('chopexec2').randomTable()
	return
```

他のchopexecの関数呼べる！　これ便利。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/ccfa1d22-6c3f-fb61-edf3-08d67d125927.png)


``` .py
def randomTable():
	for i in range(0, 8):
		melo[i] = random.randint(0,1)
		noise[i] = random.randint(0,1)
		bd[i] = random.randint(0,1)
	return
```

シンプルにランダム。

# 音色を変える

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/b1d1102f-b699-c45e-bfeb-e9fba0016dcd.png)

AudioOcillatorのTypeを変えるといろいろ音を変えられる。

Sinとかうまく組み合わせてFMっぽいのもできるのだろうか・・・


# コアとなるソース

``` chopexex2.py
# me - this DAT
# 
# channel - the Channel object which has changed
# sampleIndex - the index of the changed sample
# val - the numeric value of the changed sample
# prev - the previous sample value
# 
# Make sure the corresponding toggle is enabled in the CHOP Execute DAT.

scale = {
    0: 0,
    1: 0,
    2: 2,
    3: 2,
    4: 4,
    5: 5,
    6: 5,
    7: 7,
    8: 7,
    9: 9,
    10: 9,
    11: 12,
}

melo = {
    0: 1,
    1: 0,
    2: 0,
    3: 0,
    4: 1,
    5: 0,
    6: 0,
    7: 0,
}
noise = {
    0: 1,
    1: 1,
    2: 0,
    3: 1,
    4: 1,
    5: 1,
    6: 0,
    7: 1,
}
bd = {
    0: 1,
    1: 0,
    2: 0,
    3: 0,
    4: 1,
    5: 0,
    6: 0,
    7: 0,
}

beatNo = 0;

import random
	
def allOnTable():
	for i in range(0, 8):
		melo[i] = 1;
		noise[i] = 1;
		bd[i] = 1;
	return
		
def randomTable():
	for i in range(0, 8):
		melo[i] = random.randint(0,1)
		noise[i] = random.randint(0,1)
		bd[i] = random.randint(0,1)
	return

def onOffToOn(channel, sampleIndex, val, prev):	
	global beatNo
	#if beatNo%32 == 0:
	#	randomTable()
	beatNo+=1
	randNum = random.randint(60,72)
	randNum = scale.get(randNum%12,0)
	randNum += 60+12
	print("onOffToOn")
	if melo.get(beatNo%8,0) > 0:
		op('constant1').par.value0 = randNum;
		op('audioosci1').par.frequency = 442 * pow(2,(randNum-69)/12.0)	
		op('audioosci1').par.amp = 0.2*random.randint(0,4);
	else:		
		op('audioosci1').par.amp = 0;
	if noise.get(beatNo%8,0) > 0:
		op('audioosci2').par.amp = 0.0+0.2*random.randint(0,2);
	else:
		op('audioosci2').par.amp = 0;
	
	if bd.get(beatNo%8,0) > 0:
		op('audioosci3').par.amp = 1;
		op('audioosci3').par.frequency = 150;
	else:
		op('audioosci3').par.amp = 0;
	return

def whileOn(channel, sampleIndex, val, prev):
#	print("On")
#	if op('audioosci1').par.amp < 1:
#		op('audioosci1').par.amp = op('audioosci1').par.amp + 0.1
	op('audioosci3').par.frequency = op('audioosci3').par.frequency -8;
	return

def onOnToOff(channel, sampleIndex, val, prev):
	#print("onOnToOff")
	return

def whileOff(channel, sampleIndex, val, prev):
#	print("whileOff")
	if op('audioosci1').par.amp > 0:
		op('audioosci1').par.amp = op('audioosci1').par.amp - 2.4	
		op('audioosci1').par.frequency = op('audioosci1').par.frequency -60;	
	if op('audioosci2').par.amp > 0:
		op('audioosci2').par.amp = op('audioosci2').par.amp - 0.08
	if op('audioosci3').par.amp > 0:	
		op('audioosci3').par.amp = op('audioosci3').par.amp -0.1;
		op('audioosci3').par.frequency = op('audioosci3').par.frequency -30;
	return

def onValueChange(channel, sampleIndex, val, prev):
	print("onValueChange")
	return
	
```

# 地味にハマりどころ
![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/d26cb572-e5c3-5cfa-4059-0a0aa68f354f.png)

chopExecuteの関数はパラメータのところでOnにしておかないと呼ばれません。

あと、オーディオの処理を滑らかに行いたい場合は
「Execute For Every Sample」にしておくと良いかも。

Frame単位だとガクガクするかもしれない。（それはそれでチップチューンぽいかもですが）