Skip to content
Permalink
Browse files

MIDI implentation (#120)

* reformat. spaces

* rm unused fn

* update event system to use a union type for data

* midi plumbing complete. just needs final pass-to-lua step. WIP

* tell fn can handle up to 4 args now

* midi implementation flows through to lua. defaults to raw bytes to host

* link midi.lua into the binary for parsing fn

* fixes bug where 'retry' would be called before uart init'd

* better error messaging

* updated readme w midi implementation
  • Loading branch information
trentgill committed May 23, 2019
1 parent 8ceb85c commit 9e2d6ac8e048db3e24785ea3a08aa1ba6411d6f5
Showing with 469 additions and 330 deletions.
  1. +53 −0 README.md
  2. +40 −40 lib/events.c
  3. +10 −2 lib/events.h
  4. +71 −12 lib/lualink.c
  5. +2 −0 lib/lualink.h
  6. +116 −0 lib/midi.c
  7. +7 −0 lib/midi.h
  8. +0 −211 ll/midi.c
  9. +0 −40 ll/midi.h
  10. +102 −0 ll/midi_ll.c
  11. +41 −0 ll/midi_ll.h
  12. +2 −0 lua/crowlib.lua
  13. +25 −23 lua/input.lua
  14. +0 −2 main.c
@@ -310,6 +310,59 @@ input[1].change = function(state)
end
```

## Midi Library

crow's first Input doubles as MIDI input with the somewhat new TRS cable standard.

*nb: The pinout follows the official standard and so is compatible with Korg & Akai*
*(amongst others), but you'll need an adaptor for Novation & Arturia gear.*

Think of 'midi' as a fourth option for the Input library, albeit with very different
behaviour. Activating MIDI happens with any of the three input mode setters:
`input[1].mode = 'midi'`
or
`input[1].mode('midi')`
or
`input[1]{ mode = 'midi' }`

### Defaults for crow satellite
By default, MIDI mode simply forwards the messages to the host as raw MIDI data.
Currently all standard messages are supported except for SysEx commands.

TODO
On norns, one can treat the `crow.midi` event identically to the standard norns MIDI
events, using the `midi.to_msg` function to parse the input.

In Max the [crow] object will emit a message from the left output (midi data ...)
where `data` may be 1 to 4 numbers long. If you want to use the [midiparse] object
try: `[route midi] -> [zl iter 1] -> [midiparse]`. This will make crow feel like a
regular Max MIDI input.

### Standalone MIDI
Sure crow is great at being a midi / cv / i2c bridge to a host, but MIDI is far more
powerful when crow is running standalone. In this way your MIDI device can become
the user-interface for crow. CCs could control variables of a lua sequencer, or
notes could play Just Friends polyphonically...

The syntax for handling this behaviour is identical to norns, minus the connection
and 'ports' handling. crow only has one port! Try the below example to get started:
```
function init()
input[1].mode = 'midi;
end
input[1].midi = function(data)
local m = Midi.to_msg(data)
if m.type == 'note_on' then
print('on ' .. m.note)
elseif m.type == 'note_off' then
print('off ' .. m.note)
elseif m.type == 'cc' then
print('cc ' .. m.cc .. ' ' .. m.val)
end
end
```

## Output Library & ASL

TODO
@@ -33,8 +33,8 @@ void events_init() {

// zero out the event records
for ( k = 0; k < MAX_EVENTS; k++ ) {
sysEvents[ k ].type = 0;
sysEvents[ k ].data = 0;
sysEvents[ k ].type = 0;
sysEvents[ k ].data.i = 0;
}

// assign event handlers
@@ -43,30 +43,29 @@ void events_init() {
app_event_handlers[E_stream] = &handler_stream;
app_event_handlers[E_change] = &handler_change;
app_event_handlers[E_toward] = &handler_toward;
app_event_handlers[E_midi] = &handler_midi;
}

// get next event
// returns non-zero if an event was available
uint8_t event_next( event_t *e ) {
uint8_t status;
uint32_t old_primask = __get_PRIMASK();
__disable_irq();

// if pointers are equal, the queue is empty... don't allow idx's to wrap!
if ( getIdx != putIdx ) {
INCR_EVENT_INDEX( getIdx );
e->type = sysEvents[ getIdx ].type;
e->index = sysEvents[ getIdx ].index;
e->data = sysEvents[ getIdx ].data;
status = 1;
} else {
e->type = 0xff;
e->index = 0;
e->data = 0;
status = 0;
}

__set_PRIMASK( old_primask );
BLOCK_IRQS(
// if pointers are equal, the queue is empty... don't allow idx's to wrap!
if ( getIdx != putIdx ) {
INCR_EVENT_INDEX( getIdx );
e->type = sysEvents[ getIdx ].type;
e->index = sysEvents[ getIdx ].index;
e->data = sysEvents[ getIdx ].data;
status = 1;
} else {
e->type = 0xff;
e->index = 0;
e->data.i = 0;
status = 0;
}
);

return status;
}
@@ -75,26 +74,22 @@ uint8_t event_next( event_t *e ) {
// add event to queue, return success status
uint8_t event_post( event_t *e ) {
//printf("posting event, type: %d\n",e->type);

uint32_t old_primask = __get_PRIMASK();
__disable_irq();

uint8_t status = 0;

// increment write idx, posbily wrapping
int saveIndex = putIdx;
INCR_EVENT_INDEX( putIdx );
if ( putIdx != getIdx ) {
sysEvents[ putIdx ].type = e->type;
sysEvents[ putIdx ].index = e->index;
sysEvents[ putIdx ].data = e->data;
status = 1;
} else {
// idx wrapped, so queue is full, restore idx
putIdx = saveIndex;
}

__set_PRIMASK( old_primask );
BLOCK_IRQS(
// increment write idx, posbily wrapping
int saveIndex = putIdx;
INCR_EVENT_INDEX( putIdx );
if ( putIdx != getIdx ) {
sysEvents[ putIdx ].type = e->type;
sysEvents[ putIdx ].index = e->index;
sysEvents[ putIdx ].data = e->data;
status = 1;
} else {
// idx wrapped, so queue is full, restore idx
putIdx = saveIndex;
}
);

//if (!status)
// printf("\r\n event queue full!");
@@ -109,20 +104,25 @@ static void handler_none(event_t *e) {}

static void handler_metro(event_t *e) {
//printf("metro event\n");
L_handle_metro( e->index, e->data );
L_handle_metro( e->index, e->data.i );
}

static void handler_stream(event_t *e) {
//printf("stream event %f\n",e->data);
L_handle_in_stream( e->index, e->data );
L_handle_in_stream( e->index, e->data.f );
}

static void handler_change(event_t *e) {
//printf("change event %f\n",e->data);
L_handle_change( e->index, e->data );
L_handle_change( e->index, e->data.f );
}

static void handler_toward(event_t *e) {
//printf("toward %d\n",e->index);
L_handle_toward( e->index );
}

static void handler_midi(event_t *e) {
//printf("midi %d\n",e->data);
L_handle_midi( e->data.u8s );
}
@@ -8,13 +8,20 @@ typedef enum {
E_stream,
E_change,
E_toward,
E_midi,
E_COUNT
} event_type_t;

union Data{
int i;
float f;
uint8_t u8s[4];
};

typedef struct {
event_type_t type;
int8_t index;
double data;
int8_t index;
union Data data;
} event_t;


@@ -31,3 +38,4 @@ static void handler_metro(event_t *e);
static void handler_stream(event_t *e);
static void handler_change(event_t *e);
static void handler_toward(event_t *e);
static void handler_midi(event_t *e);
@@ -18,6 +18,7 @@
#include "../ll/random.h" // Random_Get()
#include "../ll/adda.h" // CAL_Recalibrate() CAL_PrintCalibration()
#include "lib/events.h" // event_t event_post()
#include "lib/midi.h" // MIDI_Active()

// Lua libs wrapped in C-headers: Note the extra '.h'
#include "lua/bootstrap.lua.h" // MUST LOAD THIS MANUALLY FIRST
@@ -30,6 +31,7 @@
#include "lua/ii.lua.h"
#include "build/iihelp.lua.h" // generated lua stub for loading i2c modules
#include "lua/calibrate.lua.h"
#include "lua/midi.lua.h"

#include "build/ii_lualink.h" // generated C header for linking to lua

@@ -43,6 +45,7 @@ const struct lua_lib_locator Lua_libs[] =
, { "lua_ii" , lua_ii }
, { "build_iihelp" , build_iihelp }
, { "lua_calibrate" , lua_calibrate }
, { "lua_midi" , lua_midi }
, { NULL , NULL }
};

@@ -165,6 +168,19 @@ static int _print_tell( lua_State *L )
, luaL_checkstring(L, 2)
, luaL_checkstring(L, 3) );
break;
case 4:
sprintf( teller, "^^%s(%s,%s,%s)", luaL_checkstring(L, 1)
, luaL_checkstring(L, 2)
, luaL_checkstring(L, 3)
, luaL_checkstring(L, 4) );
break;
case 5:
sprintf( teller, "^^%s(%s,%s,%s,%s)", luaL_checkstring(L, 1)
, luaL_checkstring(L, 2)
, luaL_checkstring(L, 3)
, luaL_checkstring(L, 4)
, luaL_checkstring(L, 5) );
break;
default:
return luaL_error(L, "too many args to tell.");
}
@@ -209,9 +225,10 @@ static int _set_input_none( lua_State *L )
{
uint8_t ix = luaL_checkinteger(L, 1)-1;
Detect_t* d = Detect_ix_to_p( ix ); // Lua is 1-based
if( d != NULL ){ // valid index
if(d){ // valid index
Detect_none( d );
Metro_stop( ix );
if( ix == 0 ){ MIDI_Active( 0 ); } // deactivate MIDI if first chan
}
lua_pop( L, 1 );
lua_settop(L, 0);
@@ -221,13 +238,14 @@ static int _set_input_stream( lua_State *L )
{
uint8_t ix = luaL_checkinteger(L, 1)-1;
Detect_t* d = Detect_ix_to_p( ix ); // Lua is 1-based
if( d != NULL ){ // valid index
if(d){ // valid index
Detect_none( d );
Metro_start( ix
, luaL_checknumber(L, 2)
, -1
, 0
);
if( ix == 0 ){ MIDI_Active( 0 ); } // deactivate MIDI if first chan
}
lua_pop( L, 2 );
lua_settop(L, 0);
@@ -237,19 +255,36 @@ static int _set_input_change( lua_State *L )
{
uint8_t ix = luaL_checkinteger(L, 1)-1;
Detect_t* d = Detect_ix_to_p( ix ); // Lua is 1-based
if( d != NULL ){ // valid index
if(d){ // valid index
Metro_stop( ix );
Detect_change( d
, L_queue_change
, luaL_checknumber(L, 2)
, luaL_checknumber(L, 3)
, Detect_str_to_dir( luaL_checkstring(L, 4) )
);
if( ix == 0 ){ MIDI_Active( 0 ); } // deactivate MIDI if first chan
}
lua_pop( L, 4 );
lua_settop(L, 0);
return 0;
}
static int _set_input_midi( lua_State *L )
{
uint8_t ix = luaL_checkinteger(L, 1)-1;
if( ix == 0 ){ // only first channel supports midi
Detect_t* d = Detect_ix_to_p( ix ); // Lua is 1-based
if(d){ // valid index
Detect_none( d );
Metro_stop( ix );
MIDI_Active( 1 );
}
}
lua_pop( L, 1 );
lua_settop(L, 0);
return 0;
}

static int _send_usb( lua_State *L )
{
// pattern match on type: handle values vs strings vs chunk
@@ -402,6 +437,7 @@ static const struct luaL_Reg libCrow[]=
, { "set_input_none" , _set_input_none }
, { "set_input_stream" , _set_input_stream }
, { "set_input_change" , _set_input_change }
, { "set_input_midi" , _set_input_midi }
// usb
, { "send_usb" , _send_usb }
// i2c
@@ -496,9 +532,9 @@ void L_handle_toward( int id )

void L_queue_metro( int id, int state )
{
event_t e = { .type = E_metro
, .index = id
, .data = state
event_t e = { .type = E_metro
, .index = id
, .data.i = state
};
event_post(&e);
}
@@ -516,9 +552,9 @@ void L_handle_metro( const int id, const int stage)

void L_queue_in_stream( int id )
{
event_t e = { .type = E_stream
, .index = id
, .data = IO_GetADC(id)
event_t e = { .type = E_stream
, .index = id
, .data.f = IO_GetADC(id)
};
event_post(&e);
}
@@ -536,9 +572,9 @@ void L_handle_in_stream( int id, float value )

void L_queue_change( int id, float state )
{
event_t e = { .type = E_change
, .index = id
, .data = state
event_t e = { .type = E_change
, .index = id
, .data.f = state
};
event_post(&e);
}
@@ -603,3 +639,26 @@ float L_handle_ii_followRxTx( uint8_t cmd, int args, float* data )
lua_pop( L, 1 );
return n;
}

void L_queue_midi( uint8_t* data )
{
event_t e = { .type = E_midi };
e.data.u8s[0] = data[0];
e.data.u8s[1] = data[1];
e.data.u8s[2] = data[2];
event_post(&e);
}
void L_handle_midi( uint8_t* data )
{
lua_getglobal(L, "midi_handler");
int count = MIDI_byte_count(data[0]) + 1; // +1 for cmd byte itself
for( int i=0; i<count; i++ ){
lua_pushinteger(L, data[i]);
}
if( lua_pcall(L, count, 0, 0) != LUA_OK ){
printf("midi lua-cb err\n");
Caw_send_luachunk("error: input midi");
Caw_send_luachunk( (char*)lua_tostring(L, -1) );
lua_pop( L, 1 );
}
}

0 comments on commit 9e2d6ac

Please sign in to comment.