‹ Radar Sensor f0dim ›

f0mid

2017-12-16 11:49 electronics, supercollider

Wireless MIDI <-> OSC bridge using an ESP8266-01. This circuit is extremely cheap to build. Schematics, Arduino code and examples for SuperCollider below.

I'm using the great Arduino MIDI Library that allows for both sending and receiving (see API) a multitude of MIDI messages including SysEx, system realtime and time code messages. My Arduino code just converts all these to/from OSC and send or broadcast them over WiFi network.

Note: sending MIDI over WiFi UDP is generally a bad idea. There will be delays, glitches and even lost messages (hanging notes). This is especially problematic for MIDI time code (sync) messages. That said, in many situations this is ok and in my tests with a simple note on/off messages + bend and control, things seem to work just fine.

inside the f0mid box
inside the f0mid box
inside the f0mid box

The circuit takes in 5V and then the regulator steps this down to 3.3V. Notice the huge 220uF capacitor that's needed to provide power for the ESP8266 during its infamous current draw spikes.

f0mid circuit schematics
f0mid circuit schematics
f0mid circuit schematics

SuperCollider example code...

//http://fortyseveneffects.github.io/arduino_midi_library/a00041.html
//http://fortyseveneffects.github.io/arduino_midi_library/a00043.html

OSCFunc.trace(true, true);
OSCFunc.trace(false);

n= NetAddr("f0mid.local", 18120);  //IP of ESP8266
n.sendMsg(\ip, 192, 168, 1, 99);  //receiver IP (laptop - by default this is x.x.x.255 (broadcast))
n.sendMsg(\port, 57120);  //set receiver port (by default this is 57120)

n.sendMsg(\thru, 0);  //off
n.sendMsg(\thru, 1);  //full (default)
n.sendMsg(\thru, 2);  //same channel
n.sendMsg(\thru, 3);  //different channel

n.sendMsg(\noteOn, 66, 127, 1);  //(note, velo, chan)
n.sendMsg(\noteOff, 66, 0, 1);  //(note, velo, chan)
n.sendMsg(\afterTouchPoly, 50, 60, 3);  //poly pressure (note, press, chan)
n.sendMsg(\controlChange, 1, 64, 3);  //(num, val, chan)
n.sendMsg(\programChange, 10, 4);  //(num, chan)  note the -1 offset
n.sendMsg(\afterTouchChannel, 40, 2);  //(press, chan)
n.sendMsg(\pitchBend, -8000, 1);  //(bend, chan)  -8192 - 8191
n.sendMsg(\sysEx, 240, 14, 5, 0, 5, 247);  //(sysex) - 240 a b c d e ... 247

//realtime
(
var clock= 0xf8;  //248
var start= 0xfa;  //250
var continue= 0xfb;  //251
var stop= 0xfc;  //252
Routine.run({
  n.sendMsg(\realTime, start);
  100.do{
    n.sendMsg(\realTime, clock);
    0.02.wait;
  };
  n.sendMsg(\realTime, stop);
  1.wait;
  n.sendMsg(\realTime, continue);
  100.do{
    n.sendMsg(\realTime, clock);
    0.02.wait;
  };
  n.sendMsg(\realTime, stop);
});
)
n.sendMsg(\realTime, 0xfe);  //active sensing
n.sendMsg(\realTime, 0xff);  //system reset

n.sendMsg(\songPosition, 100);
n.sendMsg(\songSelect, 3);
n.sendMsg(\tuneRequest);

n.sendMsg(\beginNrpn, 10, 3);  //(number, channel)
n.sendMsg(\nrpnDecrement, 40, 3);  //(amount, channel)
n.sendMsg(\nrpnIncrement, 30, 3);  //(amount, channel)
n.sendMsg(\endNrpn, 3);  //(channel)

n.sendMsg(\beginRpn, 10, 4);  //(number, channel)
n.sendMsg(\rpnDecrement, 40, 4);  //(amount, channel)
n.sendMsg(\rpnIncrement, 30, 4);  //(amount, channel)
n.sendMsg(\endRpn, 4);  //(channel)

//--simple MIDI synth example
(
s.latency= 0.02;
s.waitForBoot{
  var busBend= Bus.control(s);
  var busCF= Bus.control(s);
  var busRQ= Bus.control(s);
  var busVol= Bus.control(s);
  var busPan= Bus.control(s);
  busBend.value= 0;
  busCF.value= 1000;
  busRQ.value= 0.5;
  busVol.value= 0.5;
  busPan.value= 0;
  SynthDef(\note, {|freq= 400, amp= 0.5, gate= 1, busBend, busCF, busRQ, busVol, busPan|
    var env= EnvGen.ar(Env.adsr(0.01, 1, 0.85, 0.1), gate, amp, doneAction:2);
    var bend= In.kr(busBend).lag(0.01);
    var cf= In.kr(busCF).lag(0.01);
    var rq= In.kr(busRQ).lag(0.01);
    var vol= In.kr(busVol).lag(0.01);
    var pan= In.kr(busPan).lag(0.01);
    var src= BLowPass4.ar(VarSaw.ar((freq+bend).midicps), cf, rq);
    OffsetOut.ar(0, Pan2.ar(src*env, pan, vol));
  }).add;
  d= ();
  OSCdef(\f0mid, {|msg|
    switch(msg[1],
      \activeSensing, {},
      \noteOn, {
        d.at(msg[2]).set(\gate, 0);
        d.put(msg[2], Synth(\note, [
          \freq, msg[2],
          \amp, msg[3].lincurve(0, 127, 0, 0.75, 4),
          \busBend, busBend,
          \busCF, busCF,
          \busRQ, busRQ,
          \busVol, busVol,
          \busPan, busPan
        ]));
      },
      \noteOff, {
        d.at(msg[2]).set(\gate, 0);
        d.put(msg[2], nil);
      },
      \pitchBend, {
        busBend.value= msg[2]/8192;
      },
      \controlChange, {
        switch(msg[2],
          1, {
            busCF.value= msg[3].linexp(0, 127, 400, 4000);
          },
          7, {
            busVol.value= msg[3].lincurve(0, 127, 0, 1, 0);
          },
          10, {
            busPan.value= msg[3].linlin(0, 127, -1, 1);
          },
          74, {
            busRQ.value= msg[3].linlin(0, 127, 2, 0.1);
          },
          {("todo control: "+msg).postln}
        );
      },
      {("todo command: "+msg).postln}
    );
  }, \f0mid);
  CmdPeriod.doOnce({
    busBend.free;
    busCF.free;
    busRQ.free;
    busVol.free;
    busPan.free;
  });
};
)

//mtc - receive
(
var a= MIDISMPTEAssembler({|time, format, dropFrame, srcID|
  [time, format, dropFrame, srcID].postln;
  //time.postln;
});
OSCdef(\f0mid, {|msg, time, addr|
  var chan, valu;
  if(msg[1]==\mtcQF, {
    chan= msg[2].rightShift(4);  //nibble high
    valu= msg[2].bitAnd(15);  //nibble low
    if(chan==7, {
      valu= switch(valu,
        6, {valu= 96},  //30fps
        4, {valu= 64},  //30fps drop
        2, {valu= 32},  //25fps
        0, {valu= 0}    //24fps
      );
    });
    a.value(addr.addr.bitAnd(255), chan, valu);
  });
}, \f0mid);
)

//mtc - send (kind of works - wslib quark required)
(
var startSec= 0;
var t= Main.elapsedTime-startSec;
var a= SMPTE(0, 30);
Routine.run({
  var chan= 0, valu= 0;
  inf.do{
    a.newSeconds(Main.elapsedTime-t);
    switch(chan,
      0, {valu= a.frames.asInteger.bitAnd(15)},
      1, {valu= a.frames.asInteger.rightShift(4)},
      2, {valu= a.seconds.asInteger.bitAnd(15)},
      3, {valu= a.seconds.asInteger.rightShift(4)},
      4, {valu= a.minutes.asInteger.bitAnd(15)},
      5, {valu= a.minutes.asInteger.rightShift(4)},
      6, {valu= a.hours.asInteger.bitAnd(15)},
      7, {
        valu= a.hours.asInteger.bitAnd(1).rightShift(4);
        switch(a.fps,
          30, {valu= valu.bitOr(6)},  //30fps
          //30fps drop not supported
          25, {valu= valu.bitOr(2)},  //25fps
          //24, {valu= valu.bitOr(0)}  //24fps
        );
      }
    );
    n.sendMsg(\mtcQF, chan.leftShift(4)+valu.bitAnd(15));
    chan= chan+1;
    if(chan==8, {
      chan= 0;
    });
    (1/(a.fps*4)).wait;
  };
});
)

Updates:

Attachments:


‹ Radar Sensor f0dim ›