RaspberryPi上のFoxDotでMozziで作ったハードシンセを制御する

#はじめに
RaspberryPiでFoxDot動かして、音出すと同時にGPIOからCV信号出せたら
モジュラーシンセと同期して面白いんじゃないかと思いまして
(dommune見てたらそんなツイート流れてきたので、、アイデアは丸パクリ)

※モジュラーシンセ持ってないのでarduino(Mozzi)で作ります。CV信号も0～5V出せばいいんでしょ?という
大雑把な認識なのでそれにあわせたハードシンセになりそうです
これを流用しようかなと思います

使用するのはRaspberryPi2です

#SuperColliderセットアップ

RaspberryPiには、sonicPiというライブコーディング環境があって
SuperCollider-serverが入ってますがバージョンが3.7なのでFoxDotの要求に合わないので
いったん削除しておきます。

```
sudo apt remove supercollider-server
```

その後下記の通りに進めていきます

https://supercollider.github.io/development/building-raspberrypi

###必要なライブラリをインストール

```
sudo apt-get update
sudo apt-get upgrade
sudo apt-get dist-upgrade
sudo apt-get install libjack-jackd2-dev libsndfile1-dev libasound2-dev libavahi-client-dev libreadline6-dev libfftw3-dev libxt-dev libudev-dev libcwiid-dev cmake qttools5-dev-tools libqt5webkit5-dev qtpositioning5-dev libqt5sensors5-dev
```

###コンパイル＆インストール

```
git clone --recursive git://github.com/supercollider/supercollider
cd supercollider
git checkout 3.9 #use latest version 3.9.x on branch 3.9
git submodule init && git submodule update
mkdir build && cd build
cmake -L -DCMAKE_BUILD_TYPE="Release" -DBUILD_TESTING=OFF -DSUPERNOVA=OFF -DNATIVE=ON -DSC_WII=ON -DSC_IDE=ON -DSC_QT=ON -DSC_ED=OFF -DSC_EL=OFF -DSC_VIM=ON ..
make -j 4 #これはrpi3のひとだけやるみたいです
sudo make install
sudo ldconfig
```

※make installめちゃ時間かかります。。


###Jackの設定
-dhw:0を指定すると元からついているサウンドカード（ステレオジャック）で、別のUSBオーディオインターフェース使うときは-dhw:1に
します。僕はこれ　https://plugable.com/japanese/products/usb-audio/
使っているので-dhw:1にしてみました

```
/usr/bin/jackd -P75 -dalsa -dhw:1 -r44100 -p1024 -n3
```
##SuperCollider起動＆FoxDotインストール

```
scide
```
IDEが起動するので、一番右のエディタ内で

```
Quarks.install("FoxDot")
```
と入力してその行にカーソルを持って行ってctrl+ENTER

これでSuperCollider側の準備はOKです

#FoxDotインストール

pipで入るので簡単です

```
sudo pip3 install FoxDot
```



```
python3 -m FoxDot 
```
ターミナルに下のように入れるとFoxDotのコンソール開きます
（なぜだか2画面にして上がエディタ、下がログ画面？みたいなのになりません。。）
FoxDotのコンソールで

```
p1 >> pluck()
```
と入力してその行でctrl+ENTERを押して音が出ればOKです

```
p1.stop()
```
で音が止まります

#OSCサーバー

FoxDot自体からは音が出るわけではなく、FoxDot⇒SuperColliderへOSCを送っているので
別にOSCサーバーを立てて、FoxDotからOSCを受ければいいかなと考えました

※最初はこのOSCサーバーでGPIO制御してarduinoへの入力信号としようと思ったのですが
　raspberryPiのGPIO出力は3.3V、arduinoUNOは5V受け。。別の回路作るの大変なのでシリアルで制御することにしました
　つまり、、FoxDotをraspberryPiで動かす必要が無くなってしまっています。。（途中からノートPCにしました。。）

###OSCサーバー立ち上げ

```python:osc.py

"""Small example OSC server

This program listens to several addresses, and prints some information about
received packets.
"""
"""Small example OSC server

This program listens to several addresses, and prints some information about
received packets.
"""
import argparse
import math
import time
from pythonosc import dispatcher
from pythonosc import osc_server
import socket
import serial

ser=serial.Serial("/dev/ttyUSB0",115200)


def print_foxdot(unused_addr,*p):#FoxDotからのoscメッセージが2種類あるので可変長の配列で受けてます
    if (p[0]=="makeSound" or p[0]=="startSound"):
        pass
    else:
        midi=p[p.index("midinote")+1]
        mes=s+","+str(frq)+","+str(amp)
        sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
        sock.sendto(mes.encode("utf-8"),("127.0.0.1",12346))
        
        CV=int(midi).to_bytes(1,"big")
        if (midi!=0.0):
            ser.write(CV)
            
if __name__ == "__main__":
  parser = argparse.ArgumentParser()
  parser.add_argument("--ip",default="127.0.0.1", help="The ip to listen on")
  parser.add_argument("--port",type=int, default=12345, help="The port to listen on")#FoxDotの設定にあわせます
  args = parser.parse_args()

  dispatcher = dispatcher.Dispatcher()
  dispatcher.map("/s_new",print_foxdot)

  dispatcher.map("/s_new",print)#無くてもいいのですが、ちょっと画面が派手になるかなと思って受信したOSCメッセージを表示するようにしてます
  server = osc_server.ThreadingOSCUDPServer(
      (args.ip, args.port), dispatcher)
  print("Serving on {}".format(server.server_address))
  server.serve_forever()

````

###FoxDot送信先変更

この記事のとおりです。この投稿が無ければ実現しませんでした。感謝です！！！
https://qiita.com/rucochanman/items/c27a9acaa99d960df149

###Mozzi

OSCサーバーからシリアルで（0～127）のデータを受けます
もともとはMIDI-noteなのでそれを周波数に変換しています。
シンセとしては、２VCO、１LFOという感じでそれぞれ波形をsin,saw,squ,triから選べます
また一応エンベロープもつけていてアタックとディケイを触れるようにしてます。
※本当は一回信号がきたらアタック＋ディケイ＋αの期間だけ鳴るようにしたかったのですが、、
　うまくできませんでした。

```arduino:serial_mozzi.ino
#include <mozzi_midi.h>
#include <MozziGuts.h>
#include <Oscil.h> // oscillator template
#include <tables/sin2048_int8.h> // sine table for oscillator
#include <tables/saw2048_int8.h>//saw table for oscillator
#include <tables/square_analogue512_int8.h> // square table for oscillator
#include <tables/triangle2048_int8.h>//triangle table for oscillator
#include <Ead.h>
#include <EventDelay.h>
#define CONTROL_RATE 256 
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> Sin_v1(SIN2048_DATA);
Oscil <SAW2048_NUM_CELLS, AUDIO_RATE> Saw_v1(SAW2048_DATA);
Oscil <SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> Squ_v1(SQUARE_ANALOGUE512_DATA);
Oscil <TRIANGLE2048_NUM_CELLS, AUDIO_RATE> Tri_v1(TRIANGLE2048_DATA);

Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> Sin_v2(SIN2048_DATA);
Oscil <SAW2048_NUM_CELLS, AUDIO_RATE> Saw_v2(SAW2048_DATA);
Oscil <SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> Squ_v2(SQUARE_ANALOGUE512_DATA);
Oscil <TRIANGLE2048_NUM_CELLS, AUDIO_RATE> Tri_v2(TRIANGLE2048_DATA);

Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> Sin_l(SIN2048_DATA);
Oscil <SAW2048_NUM_CELLS, AUDIO_RATE> Saw_l(SAW2048_DATA);
Oscil <SQUARE_ANALOGUE512_NUM_CELLS, AUDIO_RATE> Squ_l(SQUARE_ANALOGUE512_DATA);
Oscil <TRIANGLE2048_NUM_CELLS, AUDIO_RATE> Tri_l(TRIANGLE2048_DATA);
EventDelay kDelay;
Ead kEnvelope(CONTROL_RATE);

uint8_t CV;
int gain=0;
int v1;
int v2;
int l;

void setup() {
  Serial.begin(115200);
  startMozzi(CONTROL_RATE); // set a control rate of 64 (powers of 2 please)
}

void updateControl(){
  int vfo1=mozziAnalogRead(0);
  int vfo2=mozziAnalogRead(1);
  int lfo=mozziAnalogRead(2);
  int lfo_freq=mozziAnalogRead(3);
  int attack=mozziAnalogRead(4);
  int decay=mozziAnalogRead(5);

  v1=map(vfo1,0,1023,0,3);
  v2=map(vfo2,0,1023,0,3);
  l=map(lfo,0,1023,0,3);
  int lf=map(lfo_freq,0,1023,10,100);
  int at=map(attack,0,1023,10,300);
  int de=map(decay,0,1023,50,500);


    if(Serial.available()>0){
      CV=Serial.read();
      Serial.write(CV);
    }
    int f=mtof(CV);
    switch (v1){
      case 0:
        Sin_v1.setFreq(f);
        break;
      case 1:
        Saw_v1.setFreq(f);
        break;
      case 2:
        Squ_v1.setFreq(f);
        break;
      case 3:
        Tri_v1.setFreq(f);
        break;
    }
    int f2=mtof(CV+24);
    switch (v2){
       case 0:
        Sin_v2.setFreq(f2);
        break;
      case 1:
        Saw_v2.setFreq(f2);
        break;
      case 2:
        Squ_v2.setFreq(f2);
        break;
      case 3:
        Tri_v2.setFreq(f2);
        break;
    }
    switch (l){
      case 0:
        Sin_l.setFreq(lf);
        break;
      case 1:
        Saw_l.setFreq(lf);
        break;
      case 2:
        Squ_l.setFreq(lf);
        break;
      case 3:
        Tri_l.setFreq(lf);
        break;
    }
      if (kDelay.ready()){
        kEnvelope.start(at,de);
        kDelay.start(at+de+10);
      }
      gain=(int)kEnvelope.next();
    
}

int updateAudio(){
  int aSig;
  int aSig_v1;
  int aSig_v2;
  int aSig_l;
  switch (v1){
    case 0:
      aSig_v1=Sin_v1.next();
      break;
    case 1:
      aSig_v1=Saw_v1.next();
      break;
    case 2:
      aSig_v1=Squ_v1.next();
      break;
    case 3:
      aSig_v1=Tri_v1.next();
      break;
    }
  switch (v2){
    case 0:
      aSig_v2=Sin_v2.next();
      break;
    case 1:
      aSig_v2=Saw_v2.next();
      break;
    case 2:
      aSig_v2=Squ_v2.next();
      break;
    case 3:
      aSig_v2=Tri_v2.next();
      break;
    }
  switch (l){
    case 0:
      aSig_l=Sin_l.next();
      break;
    case 1:
      aSig_l=Saw_l.next();
      break;
    case 2:
      aSig_l=Squ_l.next();
      break;
    case 3:
      aSig_l=Tri_l.next();
      break;
    }
  aSig=((aSig_v1+aSig_v2)/4*aSig_l)>>4;
  aSig=gain*aSig;
  return aSig;
}

void loop() {
    audioHook();
}

````

###デモ

ハードシンセ側は結構盛大なノイズになったと思いますが、もう少し音にバリエーション出来るといいなぁと思います

https://youtu.be/Q_Kk0-MjuEE