LoginSignup
4
2

More than 5 years have passed since last update.

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

Posted at

はじめに

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

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

使用するのはRaspberryPi2です

SuperColliderセットアップ

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

sudo apt remove supercollider-server

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

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

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サーバー立ち上げ

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なのでそれを周波数に変換しています。
シンセとしては、2VCO、1LFOという感じでそれぞれ波形をsin,saw,squ,triから選べます
また一応エンベロープもつけていてアタックとディケイを触れるようにしてます。
※本当は一回信号がきたらアタック+ディケイ+αの期間だけ鳴るようにしたかったのですが、、
 うまくできませんでした。

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();
}

デモ

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

4
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2