f0mid

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 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 specially problematic for midi time code (sync) messages. that said, in many situations this is ok and in my tests with simple note on/off messages + bend and control, things seem to work just fine.

f0mid

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. it wouldn't be hard to add a midi thru section if needed.

f0mid_schematics

supercollider example code...

//http://arduinomidilib.fortyseveneffects.com/a00041.html
//http://arduinomidilib.fortyseveneffects.com/a00043.html

OSCFunc.trace(true, true);  //your laptop should have ip x.x.x.99 to receive - check in the arduino code
OSCFunc.trace(false);

n= NetAddr("192.168.1.100", 18120);  //ip of esp8266

n.sendMsg(\ip, 192, 168, 1, 99);  //set receiver ip (the computer sc is running on - by default this is x.x.x.99)
n.sendMsg(\port, 57120);  //set receiver port (by default this is 57120)

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

update 180421: added soft access-point option as well as osc commands for setting the receiver ip and port

AttachmentSize
Package icon f0mid_firmware.zip3.7 KB