Convert MIDI to OSC

I am new to the forum so first of all let me say hi to everyone!

I am trying to use SonicPi to route MIDI messages from a [trellis](https://learn.adafruit.com/adafruit-trellis-diy-open-source-led-keypad/overview) board and convert them to OSC messages to be used in a light software.

So far I have looked inside the forum and didn’t see any similar project, and have tried myself the following without success:

use_osc "localhost",8000

live_loop :listen do
  use_real_time
  cc = sync "/midi/trellis_arduino_leonardo/0/1/control_change"
  puts cc
  if cc = 102
    osc "/hello"
  else
    osc "/bye"
  end
  sleep 0.5
end

The main idea is to assignate midi CC values to corresponding OSC messages.
Thanks in advance if anyone can give some light on this or has worked on sth similar!

Hi @marccobe,

oh, that would be great if the Trellis could be connected via USB and exchange OSC directly like a standard USB midi device. I am afraid it is not that easy (at least not to my knowledge). There is a thread at Lines, where I did ask for help to get me going with my Adafruit Unztrument (which is based on Trellis key pads).

I have it running with a serialoscd implementation by Szymon Kaliski. This works based on nodejs, so I would have to use Javascript to link the Unztrument to Sonic Pi.

I am still looking for a solution to use the ‘standard’ serialoscd and use the Unztrument like a monome clone. But this seems to require to flash the Arduino’s FTDI chip (see a blog entry of Szymon, where he explains why he reimplemented serialoscd: “… either flash my Arduino FTDI eeprom …”).

I did try to flash my FTDI chip but did not suceed yet. So right now I am also stuck with my Unztrument, because it does not communicate like any other standard USB midi device.

Hi @Martin,

First of all thanks for your reply. I have already looked at most of the stuff you’re linking above before, but still haven’t find a good solution on how to convert Unztrument midi messages into OSC.
In my case the Mini Unztrument is working fine as any standard usb midi device. I have followed Adafruit guide (specially when installing teensyduino for uploading midi code to Leonardo) and used the code below, which was slightly changed from the original, which wasn’t working in my case.

/****************************************************************************
MIT License
Copyright (c) 2018 gdsports625@gmail.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
****************************************************************************/

/*
 * Mini Untztrument Demo, Arduino Style
 *
 * https://learn.adafruit.com/mini-untztrument-3d-printed-midi-controller
 *
 * miniuntz is based on the code at the above link but it does not depend on
 * Teensy USB code. It uses the Arduino MIDIUSB library by Gary Grewal which
 * simplifies the software installation.
 *
 * WARNING: Do not enable ANALOG_INPUT unless pots are hooked up to the analog
 * inputs. If nothing is connected, the analog inputs will pick up random noise
 * which will result in a never ending stream of MIDI CC messages.
 *
 * miniuntz has been tested on the SparkFun Pro Micro 5V which has the same
 * processor (32u4) as the Leonardo. The Adafruit Itsy Bitsy 5V also has a 32u4
 * processor so it should also work.
 *
 * Trellis  Pro Micro
 * =======  =========
 * 5V       RAW
 * GND      GND
 * SDA      2
 * SCL      3
 * INT      not connected
 *
 * miniuntz has been tested on the Adafruit Trinket M0 with bidirectional logic
 * level changer.  Analog inputs not tested. But the Trinket M0 should have 3
 * inputs for analog input, if desired.  This should also work with the Itsy
 * Bitsy M0, Feather M0, SparkFun SAMD21, and Arduino Zero. All use 3.3V logic
 * levels so a logic level converter is required.
 *
 * Trellis     LLCONV      Trinket M0
 * =======     ======      =======
 *                  LV     3.3
 * 5V          HV          USB
 * GND         GND-GND     GND
 * SCL         HV1-LV1     SCL
 * SDA         HV2-LV2     SDA
 * INT         HV3-LV3     not connected
 *
 * LLCONV = bidirectional logic level converter such as Adafruit BSS138 board.
 *  https://www.adafruit.com/product/757
 */

#include <Wire.h>
#include <Adafruit_Trellis.h>
#include <MIDIUSB.h>

#define LED     LED_BUILTIN // Pin for heartbeat LED (shows code is working)
#define CHANNEL 0           // MIDI channel number

Adafruit_Trellis trellis;

/*
 * Do not enable without connecting pots to the inputs.
 */
//#define ANALOG_INPUT

uint8_t       heart        = 0;  // Heartbeat LED counter
unsigned long prevReadTime = 0L; // Keypad polling timer
#ifdef ANALOG_INPUT
uint8_t       mod;
uint8_t       vel;
uint8_t       fxc;
uint8_t       rate;
#endif

uint8_t note[] = {
  60, 61, 62, 63,
  56, 57, 58, 59,
  52, 53, 54, 55,
  48, 49, 50, 51
};

// First parameter is the event type (0x09 = note on, 0x08 = note off).
// Second parameter is note-on/note-off, combined with the channel.
// Channel can be anything between 0-15. Typically reported to the user as 1-16.
// Third parameter is the note number (48 = middle C).
// Fourth parameter is the velocity (64 = normal, 127 = fastest).

void noteOn(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOn = {0x09, (byte)(0x90 | channel), pitch, velocity};
  MidiUSB.sendMIDI(noteOn);
}

void noteOff(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOff = {0x08, (byte)(0x80 | channel), pitch, velocity};
  MidiUSB.sendMIDI(noteOff);
}

// First parameter is the event type (0x0B = control change).
// Second parameter is the event type, combined with the channel.
// Third parameter is the control number number (0-119).
// Fourth parameter is the control value (0-127).

void controlChange(byte channel, byte control, byte value) {
  midiEventPacket_t event = {0x0B, (byte) (0xB0 | channel), control, value};
  MidiUSB.sendMIDI(event);
}

void setup() {
  pinMode(LED, OUTPUT);
  trellis.begin(0x70); // Pass I2C address
#ifdef __AVR__
  // Default Arduino I2C speed is 100 KHz, but the HT16K33 supports
  // 400 KHz.  We can force this for faster read & refresh, but may
  // break compatibility with other I2C devices...so be prepared to
  // comment this out, or save & restore value as needed.
  TWBR = 12;
#endif
  trellis.clear();
  trellis.writeDisplay();
#ifdef ANALOG_INPUT
  mod = map(analogRead(0), 0, 1023, 0, 127);
  vel = map(analogRead(1), 0, 1023, 0, 127);
  fxc = map(analogRead(2), 0, 1023, 0, 127);
  rate = map(analogRead(3),0, 1023, 0, 127);
  controlChange(CHANNEL,  1, mod);
  controlChange(CHANNEL, 11, vel);
  controlChange(CHANNEL, 12, fxc);
  controlChange(CHANNEL, 13, rate);
#endif
}

void loop() {
  unsigned long t = millis();
  if((t - prevReadTime) >= 20L) { // 20ms = min Trellis poll time
    if(trellis.readSwitches()) {  // Button state change?

      for(uint8_t i=0; i<16; i++) { // For each button...
        if(trellis.justPressed(i)) {
          noteOn(CHANNEL, note[i], 127);

          trellis.setLED(i);
        } else if(trellis.justReleased(i)) {
          noteOn(CHANNEL, note[i], 0);
          trellis.clrLED(i);
        }
      }
      trellis.writeDisplay();
    }
#ifdef ANALOG_INPUT
    uint8_t newModulation = map(analogRead(0), 0, 1023, 0, 127);
    if(mod != newModulation) {
      mod = newModulation;
      controlChange(CHANNEL, 1, mod);
    }
    uint8_t newVelocity = map(analogRead(1), 0, 1023, 0, 127);
    if(vel != newVelocity) {
      vel = newVelocity;
      controlChange(CHANNEL, 11, vel);
    }
    uint8_t newEffect = map(analogRead(2), 0, 1023, 0, 127);
    if(fxc != newEffect) {
      fxc = newEffect;
      controlChange(CHANNEL, 12, fxc);
    }
    uint8_t newRate = map(analogRead(3), 0, 1023, 0, 127);
    if(rate !=newRate) {
      rate = newRate;
      controlChange(CHANNEL, 13, rate);
    }
#endif
    prevReadTime = t;
    digitalWrite(LED, ++heart & 32); // Blink = alive
    MidiUSB.flush();
  }
  (void)MidiUSB.read(); // Discard incoming MIDI messages
}

So far I am able to get midi messages on SonicPi with my MiniUnztrument but have no clue on how to turn them into OSC messages. I have tried with other programs and different platforms and the MiniUnztrument is working as a USB classic MIDI device in all of them.
Hope it is clearer now.

Thanks!

Hi @marccobe,

yes, midi is not the problem - also for me. Let us know if you find out how to use OSC. So far I think the solution for me would be if I could teach the Unztrument to behave like a monome…

OK, I’ll let you know. My idea was to use SonicPi as a router and converter from midi (Untztrument) to OSC (other programs and platforms)

Is osmid any use? It is already in use within Sonic Pi and converts midi->osc and vice versa.
See here

hello @robin.newman
Looks very interesting, indeed. If I don’t get wrong osmid acts basically as a “library” for SonicPi. Does the programming needs to be done in the same interface right? It just allows the use of new vocabulary, like in Arduino for instance, right?
I’ll give it a try!

Thanks!

osmid can work independently from Sonic Pi. I think you can have more than one instance running if you choose prots appropriately. Sonic Pi talks to it via OSC links. Separate binaries work for each direction m2o (Midi to OSC) and o2m (OSC to midi) In Sonic Pi erlang also comes ito the mix handling the scheduling of midi commands.
There are others more informed than I am in such matters!

1 Like

Hi there @marccobe,

welcome to our forums :slight_smile:

When you say “without success” could you let me know what you observe and what you wished you had observed?

Hi @samaaron and thanks for welcoming.

I have tried the code posted above but it’s not working as I intended. It actually does convert incoming midi messages to OSC but of all them are the same (in my case: /hello)
The idea behind and what I’d like to achieve is to have different OSC messages for every different button in the pad, so that every CC/midi note has assigned a particular OSC message.

I actually don’t know how to discriminate between notes. In my case I tried:

  cc = sync "/midi/trellis_arduino_leonardo/0/1/control_change"
  puts cc
  if cc = 102
    osc "/hello"
  else
    osc "/bye"

The idea was to discriminate CC values from the controller but that doesn’t seem to work this way. I imagine there are other better ways to do this…

One obvious bug in your code is that you’re using:

cc = 102

Which is the assignment operator rather than a test for inequality. Try replacing that with:

cc == 102

Although from memory, I think that the value of cc is likely to be a list of two numbers, the control ID and the value. For example, something like: [32, 120].

If this is what you’re seeing as the result of puts cc, then you need to do a bit more work to extract the id and the value separately. Something like this might work:

id, val = sync "/midi/trellis_arduino_leonardo/0/1/control_change"
puts "id: #{id}, val: #{val}" # just to help debug
if id == 102
  osc "/hello"
else
  osc "/bye"
end

Let me know if this helps :slight_smile:

1 Like

That worked perfectly.
I have adjusted it so that the button only triggers the OSC message when pressed.

use_osc "localhost",8000

live_loop :listen do
  use_real_time
  
  id, val = sync "/midi/trellis_arduino_leonardo/0/1/control_change"
  
  puts "id: #{id}, val: #{val}" # just to help debug
  
  if (id == 102 and val == 127)
    
    osc "/hello102"
    
  end
  
  if (id == 103 and val == 127)
    
    osc "/hello103"
    
  end
  
  sleep 0.5
end

There will be as many “if” statements as buttons so that each has its own configurable OSC message.
Thanks a lot @samaaron :smile:

Excellent!

Two extra tips:

Firstly, you likely don’t need the sleep command at the bottom (unless you want to throttle input). As it currently stands you can only process 2 incoming messages per second. Removing the sleep will allow you to process more messages.

Secondly, you might want to consider a case statement rather than many ifs. For that you can just use the returned [id, val] list directly:

use_osc "localhost",8000

live_loop :listen do
  use_real_time
  cc = sync "/midi/trellis_arduino_leonardo/0/1/control_change"
  puts cc # just to help debug
  
  case cc
  when [102, 127] 
    osc "/hello102"
  when [103, 127]
    osc "/hello103"
  else
    puts "Unknown CC message"
  end
end
3 Likes

It looks more neat indeed, and working perfect with all the cc midi statements (16 actually).
I will keep the sleep function anyway, as it filters any unwanted button to be pressed after another.
Thanks a lot @samaaron and the rest. It looks like you guys are really eager to help in this forum! That’s always good to see!

2 Likes

Hi

I’m trying to build a midi controller for the motu-avb, and this is part of the solution I’ve been looking for. So far I can toggle mute and solo with my Novation Sl controller, but I’m stuck with the fader implementation. The AVB mixer fader can take values from 0.00 to 4.00. How can I convert midi cc values from 0-127 and send them to the correct AVB fader values?

use_osc "lp32.local", 9998

live_loop :listen do
  use_real_time
  cc = sync "/midi:remote_zero_sl_anschluss_1:1/control_change"
  puts cc
  
  case cc
  #Solo Toggle
  when [29, 127]
    osc "/mix/chan/0/matrix/solo", 0
  when [29, 0]
    osc "/mix/chan/0/matrix/solo", 1
  #Mute Toggle
  when [37, 127]
    osc "/mix/chan/0/matrix/mute", 0
  when [37, 0]
    osc "/mix/chan/0/matrix/mute", 1
  #Fader
  when [21, 0]
    osc "/mix/chan/0/matrix/fader", 0
  when [21, 127]
    osc "/mix/chan/0/matrix/fader", 4
  end
end

Hi Mike
Perhaps you can find some hints for the fader in this code.
It has been a long time since I used it.
Kind regards
Relaxnow