~hp/chimaera_firmware

bb953069475b834a4b07f979e21b4ce1f746430d — Hanspeter Portner 8 years ago fd0f0ba
implement MIDI MPE mode in oscmidi engine.
M cmc/cmc.c => cmc/cmc.c +27 -6
@@ 40,6 40,7 @@
CMC_Engine *engines [ENGINE_MAX+1];
uint_fast8_t cmc_engines_active = 0;
CMC_Group *cmc_groups = config.groups;
uint16_t cmc_groups_n = GROUP_MAX;

#ifdef BENCHMARK
Stop_Watch sw_engine_process = {.id = "engine_process", .thresh=3000};


@@ 75,8 76,6 @@ static uint8_t va[SENSOR_N+2];
static CMC_Blob blobs[2][BLOB_MAX];
static uint8_t pacemaker = 0x0b; // pacemaker rate 2^11=2048

static void cmc_engines_init(void);

void
cmc_velocity_stiffness_update(uint8_t stiffness)
{


@@ 111,8 110,8 @@ cmc_init(void)
	cmc_old = blobs[old];
	cmc_neu = blobs[neu];

	// initialize output engines
	cmc_engines_init();
	// update group and initialize output engines
	cmc_group_update();

	// update engines stack
	cmc_engines_update();


@@ 775,7 774,7 @@ cmc_process(OSC_Timetag now, OSC_Timetag offset, int16_t *rela, osc_data_t *buf,
}

void 
cmc_group_clear(void)
cmc_group_reset(void)
{
	uint16_t gid;
	for(gid=0; gid<GROUP_MAX; gid++)


@@ 783,11 782,17 @@ cmc_group_clear(void)
		CMC_Group *grp = &cmc_groups[gid];

		grp->gid = gid;
		grp->pid = CMC_BOTH;
		grp->pid = 0;
		grp->x0 = 0.0;
		grp->x1 = 1.0;
		grp->m = CMC_NOSCALE;
	}

	// define two groups by default
	cmc_groups[0].pid = CMC_SOUTH;
	cmc_groups[1].pid = CMC_NORTH;

	cmc_group_update();
}

static void


@@ 813,6 818,22 @@ cmc_engines_init(void)
}

void
cmc_group_update(void)
{
	uint16_t gid;
	for(gid=0; gid<GROUP_MAX; gid++)
	{
		if(!cmc_groups[gid].pid)
			break; // first non-responding group found
	}

	cmc_groups_n = gid;

	// reinitialize engines (e.g. oscmidi needs that for MPE mode)
	cmc_engines_init();
}

void
cmc_engines_update(void)
{
	cmc_engines_active = 0;

M config/config.c => config/config.c +1 -0
@@ 97,6 97,7 @@ Config config = {
		.enabled = 0,
		.multi = 1,
		.format = OSC_MIDI_FORMAT_MIDI,
		.mpe = 0,
		.path = {'/', 'm', 'i', 'd', 'i', '\0'}
	},


M engines/oscmidi.h => engines/oscmidi.h +1 -1
@@ 45,6 45,6 @@ struct _OSC_MIDI_Group {

extern OSC_MIDI_Group *oscmidi_groups;
extern CMC_Engine oscmidi_engine;
extern const OSC_Query_Item oscmidi_tree [6];
extern const OSC_Query_Item oscmidi_tree [7];

#endif // _OSCMIDI_H_

M include/cmc.h => include/cmc.h +3 -1
@@ 77,13 77,15 @@ struct _CMC_Blob_Event {
};

extern CMC_Group *cmc_groups;
extern uint16_t cmc_groups_n;
extern uint_fast8_t cmc_engines_active;

void cmc_velocity_stiffness_update(uint8_t stiffness);
void cmc_init(void);
osc_data_t *cmc_process(OSC_Timetag now, OSC_Timetag offset, int16_t *rela, osc_data_t *buf, osc_data_t *end);

void cmc_group_clear(void);
void cmc_group_reset(void);
void cmc_group_update(void);
void cmc_engines_update(void);

#endif // _CMC_H_

M include/config.h => include/config.h +1 -0
@@ 120,6 120,7 @@ struct _Config {
		uint8_t enabled;
		uint8_t multi;
		uint8_t format;
		uint8_t mpe;
		char path [64];
	} oscmidi;


M include/midi.h => include/midi.h +31 -4
@@ 33,11 33,16 @@ enum _MIDI_COMMAND {
	
	MIDI_CONTROLLER_MODULATION				= 0x01,
	MIDI_CONTROLLER_BREATH						= 0x02,
	MIDI_CONTROLLER_DATA_ENTRY				= 0x06,  /**< Data Entry */

	MIDI_CONTROLLER_VOLUME						= 0x07,
	MIDI_CONTROLLER_PAN								= 0x0a,
	MIDI_CONTROLLER_EXPRESSION				= 0x0b,
	MIDI_CONTROLLER_EFFECT_CONTROL_1	= 0x0c,
	MIDI_CONTROLLER_EFFECT_CONTROL_2	= 0x0d,
	MIDI_CONTROLLER_RPN_LSB						= 0x64,  /**< Registered Parameter Number */
	MIDI_CONTROLLER_RPN_MSB						= 0x65,  /**< Registered Parameter Number */


	MIDI_CONTROLLER_ALL_NOTES_OFF			= 0x7b,
};


@@ 49,17 54,39 @@ typedef struct _MIDI_Hash MIDI_Hash;

struct _MIDI_Hash {
	uint32_t sid;
	uint8_t cha;
	uint8_t key;
	uint8_t pos;
};

void midi_add_key(MIDI_Hash *hash, uint32_t sid, uint8_t key);
uint8_t midi_get_key(MIDI_Hash *hash, uint32_t sid);
uint8_t midi_rem_key(MIDI_Hash *hash, uint32_t sid);
void midi_add_key(MIDI_Hash *hash, uint32_t sid, uint8_t key, uint8_t cha);
uint8_t midi_get_key(MIDI_Hash *hash, uint32_t sid, uint8_t *key, uint8_t *cha);
uint8_t midi_rem_key(MIDI_Hash *hash, uint32_t sid, uint8_t *key, uint8_t *cha);

//TODO create a MIDI meta engine, both OSC-MIDI and RTP-MIDI can refer to

#define MIDI_BOT (3.f*12.f - 0.5f - (SENSOR_N % 18 / 6.f))
#define MIDI_RANGE (SENSOR_N/3.f)

#define CHAN_MAX 16
#define ZONE_MAX (CHAN_MAX / 2)

typedef struct _zone_t zone_t;
typedef struct _mpe_t mpe_t;

struct _zone_t {
	uint8_t base;
	uint8_t span;
	uint8_t ref;
};

struct _mpe_t {
	uint8_t n_zones;
	zone_t zones [ZONE_MAX];
	int8_t channels [CHAN_MAX];
};

void mpe_populate(mpe_t *mpe, uint8_t n_zones);
uint8_t mpe_acquire(mpe_t *mpe, uint8_t zone_idx);
void mpe_release(mpe_t *mpe, uint8_t zone_idx, uint8_t ch);

#endif // _MIDI_H_ 

M midi/midi.c => midi/midi.c +91 -8
@@ 19,7 19,7 @@
#include <midi.h>

inline void
midi_add_key(MIDI_Hash *hash, uint32_t sid, uint8_t key)
midi_add_key(MIDI_Hash *hash, uint32_t sid, uint8_t key, uint8_t cha)
{
	uint_fast8_t k;
	for(k=0; k<BLOB_MAX; k++)


@@ 27,30 27,113 @@ midi_add_key(MIDI_Hash *hash, uint32_t sid, uint8_t key)
		{
			hash[k].sid = sid;
			hash[k].key = key;
			hash[k].pos = 0; //FIXME
			hash[k].cha = cha;

			break;
		}
}

inline uint8_t
midi_get_key(MIDI_Hash *hash, uint32_t sid)
midi_get_key(MIDI_Hash *hash, uint32_t sid, uint8_t *key, uint8_t *ch)
{
	uint_fast8_t k;
	for(k=0; k<BLOB_MAX; k++)
		if(hash[k].sid == sid)
			return hash[k].key;
	return 0; // not found
		{
			*key = hash[k].key;
			*ch = hash[k].cha;

			return 0; // success
		}
	return 1; // not found
}

inline uint8_t
midi_rem_key(MIDI_Hash *hash, uint32_t sid)
midi_rem_key(MIDI_Hash *hash, uint32_t sid, uint8_t *key, uint8_t *ch)
{
	uint_fast8_t k;
	for(k=0; k<BLOB_MAX; k++)
		if(hash[k].sid == sid)
		{
			hash[k].sid = 0;
			return hash[k].key;
			*key = hash[k].key;
			*ch = hash[k].cha;

			return 0; // success
		}
	return 1; // not found
}

inline void
mpe_populate(mpe_t *mpe, uint8_t n_zones)
{
	n_zones %= ZONE_MAX; // wrap around if n_zones > ZONE_MAX
	int8_t rem = CHAN_MAX % n_zones;
	const uint8_t span = (CHAN_MAX - rem) / n_zones - 1;
	uint8_t ptr = 0;

	mpe->n_zones = n_zones;
	zone_t *zones = mpe->zones;
	int8_t *channels = mpe->channels;

	for(uint8_t i=0;
		i<n_zones;
		rem--, ptr += 1 + zones[i++].span)
	{
		zones[i].base = ptr;
		zones[i].ref = 0;
		zones[i].span = span;
		if(rem > 0)
			zones[i].span += 1;
	}

	for(uint8_t i=0; i<CHAN_MAX; i++)
		channels[i] = 0;
}

inline uint8_t
mpe_acquire(mpe_t *mpe, uint8_t zone_idx)
{
	zone_idx %= mpe->n_zones; // wrap around if zone_idx > n_zones
	zone_t *zone = &mpe->zones[zone_idx];
	int8_t *channels = mpe->channels;

	int8_t min = INT8_MAX;
	uint8_t pos = zone->ref; // start search at current channel
	const uint8_t base_1 = zone->base + 1;
	for(uint8_t i = zone->ref; i < zone->ref + zone->span; i++)
	{
		const uint8_t ch = base_1 + (i % zone->span); // wrap to [0..span]
		if(channels[ch] < min) // check for less occupation
		{
			min = channels[ch]; // lower minimum
			pos = i; // set new minimally occupied channel
		}
	return 0; // not found
	}

	const uint8_t ch = base_1 + (pos % zone->span); // wrap to [0..span]
	if(channels[ch] <= 0) // off since long
		channels[ch] = 1;
	else
		channels[ch] += 1; // increase occupation
	zone->ref = (pos + 1) % zone->span; // start next search from next channel

	return ch;
}

inline void
mpe_release(mpe_t *mpe, uint8_t zone_idx, uint8_t ch)
{
	zone_idx %= mpe->n_zones; // wrap around if zone_idx > n_zones
	ch %= CHAN_MAX; // wrap around if ch > CHAN_MAX
	zone_t *zone = &mpe->zones[zone_idx];
	int8_t *channels = mpe->channels;

	const uint8_t base_1 = zone->base + 1;
	for(uint8_t i = base_1; i < base_1 + zone->span; i++)
	{
		if( (i == ch) || (channels[i] <= 0) )
			channels[i] -= 1;
		// do not decrease occupied channels
	}
}

M oscmidi/oscmidi.c => oscmidi/oscmidi.c +89 -9
@@ 45,17 45,31 @@ static const char *oscmidi_fmt_1 [] = {
};

static MIDI_Hash oscmidi_hash [BLOB_MAX];
static mpe_t mpe;

static osc_data_t *pack;
static osc_data_t *bndl;

static uint_fast8_t update_zones = 0;

static void
oscmidi_init(void)
{
	uint_fast8_t i;
	OSC_MIDI_Group *group = config.oscmidi_groups;
	for(i=0; i<GROUP_MAX; i++, group++)
		mul[i] = (float)0x1fff / group->range;
	{
		if(config.oscmidi.mpe)
			mul[i] = (float)0x1fff / ceil(group->range); //MPE only supports whole seminote ranges
		else
			mul[i] = (float)0x1fff / group->range;
	}

	// populate mpe struct
	mpe_populate(&mpe, cmc_groups_n);

	// only update zones when mpe is activated
	update_zones = config.oscmidi.mpe;
}

static osc_data_t *


@@ 109,6 123,41 @@ oscmidi_engine_frame_cb(osc_data_t *buf, osc_data_t *end, CMC_Frame_Event *fev)
		buf_ptr = osc_start_bundle_item(buf_ptr, end, &pack);
	buf_ptr = osc_start_bundle(buf_ptr, end, fev->offset, &bndl);

	if(update_zones)
	{
		OSC_MIDI_Format format = config.oscmidi.format;
		uint_fast8_t multi = config.oscmidi.multi;
		osc_data_t *itm = NULL;

		for(uint8_t z=0; z<cmc_groups_n; z++)
		{
			if(multi)
			{
				buf_ptr = osc_start_bundle_item(buf_ptr, end, &itm);
				buf_ptr = osc_set_path(buf_ptr, end, config.oscmidi.path);
				buf_ptr = osc_set_fmt(buf_ptr, end, "mmmmmm");
			}

			const zone_t *zone = &mpe.zones[z];

			// define zone span
			buf_ptr = oscmidi_serialize(buf_ptr, end, format, zone->base, MIDI_STATUS_CONTROL_CHANGE, MIDI_CONTROLLER_RPN_LSB, 0x6);
			buf_ptr = oscmidi_serialize(buf_ptr, end, format, zone->base, MIDI_STATUS_CONTROL_CHANGE, MIDI_CONTROLLER_RPN_MSB, 0x0);
			buf_ptr = oscmidi_serialize(buf_ptr, end, format, zone->base, MIDI_STATUS_CONTROL_CHANGE, MIDI_CONTROLLER_DATA_ENTRY, zone->span);

			// define zone bend range
			buf_ptr = oscmidi_serialize(buf_ptr, end, format, zone->base+1, MIDI_STATUS_CONTROL_CHANGE, MIDI_CONTROLLER_RPN_LSB, 0x0);
			buf_ptr = oscmidi_serialize(buf_ptr, end, format, zone->base+1, MIDI_STATUS_CONTROL_CHANGE, MIDI_CONTROLLER_RPN_MSB, 0x0);
			const uint8_t semitone_range = ceil(oscmidi_groups[z].range);
			buf_ptr = oscmidi_serialize(buf_ptr, end, format, zone->base+1, MIDI_STATUS_CONTROL_CHANGE, MIDI_CONTROLLER_DATA_ENTRY, semitone_range);

			if(multi)
				buf_ptr = osc_end_bundle_item(buf_ptr, end, itm);
		}

		update_zones = 0;
	}

	return buf_ptr;
}



@@ 132,6 181,7 @@ oscmidi_engine_on_cb(osc_data_t *buf, osc_data_t *end, CMC_Blob_Event *bev)
	osc_data_t *itm = NULL;
	OSC_MIDI_Format format = config.oscmidi.format;
	uint_fast8_t multi = config.oscmidi.multi;
	uint_fast8_t use_mpe = config.oscmidi.mpe;
	OSC_MIDI_Group *group = &oscmidi_groups[bev->gid];
	OSC_MIDI_Mapping mapping = group->mapping;



@@ 145,10 195,14 @@ oscmidi_engine_on_cb(osc_data_t *buf, osc_data_t *end, CMC_Blob_Event *bev)
			buf_ptr = osc_set_fmt(buf_ptr, end, oscmidi_fmt_3[format]);
	}

	uint8_t ch = bev->gid % 0xf;
	uint8_t ch;
	if(use_mpe)
		ch = mpe_acquire(&mpe, bev->gid);
	else
		ch = bev->gid;
	float X = group->offset + bev->x*group->range;
	uint8_t key = floor(X);
	midi_add_key(oscmidi_hash, bev->sid, key);
	midi_add_key(oscmidi_hash, bev->sid, key, ch);

	uint16_t bend =(X - key)*mul[bev->gid] + 0x1fff;
	uint16_t eff = bev->y * 0x3fff;


@@ 184,6 238,7 @@ oscmidi_engine_off_cb(osc_data_t *buf, osc_data_t *end, CMC_Blob_Event *bev)
	osc_data_t *itm = NULL;
	OSC_MIDI_Format format = config.oscmidi.format;
	uint_fast8_t multi = config.oscmidi.multi;
	uint_fast8_t use_mpe = config.oscmidi.mpe;

	if(multi)
	{


@@ 192,8 247,11 @@ oscmidi_engine_off_cb(osc_data_t *buf, osc_data_t *end, CMC_Blob_Event *bev)
		buf_ptr = osc_set_fmt(buf_ptr, end, oscmidi_fmt_1[format]);
	}

	uint8_t ch = bev->gid % 0xf;
	uint8_t key = midi_rem_key(oscmidi_hash, bev->sid);
	uint8_t key;
	uint8_t ch;
	midi_rem_key(oscmidi_hash, bev->sid, &key, &ch);
	if(use_mpe)
		mpe_release(&mpe, bev->gid, ch);

	// serialize
	buf_ptr = oscmidi_serialize(buf_ptr, end, format, ch, MIDI_STATUS_NOTE_OFF, key, 0x7f);


@@ 224,9 282,10 @@ oscmidi_engine_set_cb(osc_data_t *buf, osc_data_t *end, CMC_Blob_Event *bev)
			buf_ptr = osc_set_fmt(buf_ptr, end, oscmidi_fmt_2[format]);
	}

	uint8_t ch = bev->gid % 0xf;
	float X = group->offset + bev->x*group->range;
	uint8_t key = midi_get_key(oscmidi_hash, bev->sid);
	uint8_t key;
	uint8_t ch;
	midi_get_key(oscmidi_hash, bev->sid, &key, &ch);
	uint16_t bend =(X - key)*mul[bev->gid] + 0x1fff;
	uint16_t eff = bev->y * 0x3fff;



@@ 281,6 340,15 @@ _oscmidi_multi(const char *path, const char *fmt, uint_fast8_t argc, osc_data_t 
}

static uint_fast8_t
_oscmidi_mpe(const char *path, const char *fmt, uint_fast8_t argc, osc_data_t *buf)
{
	uint8_t res = config_check_bool(path, fmt, argc, buf, &config.oscmidi.mpe);
	if( (argc > 1) && config.oscmidi.mpe)
		oscmidi_init(); // send zones
	return res;
}

static uint_fast8_t
_oscmidi_path(const char *path, const char *fmt, uint_fast8_t argc, osc_data_t *buf)
{
	(void)fmt;


@@ 370,12 438,18 @@ _oscmidi_reset(const char *path, const char *fmt, uint_fast8_t argc, osc_data_t 
		group->control = 0x07;
		group->offset = MIDI_BOT;
		group->range = MIDI_RANGE;
		mul[i] = (float)0x1fff / group->range;
		if(config.oscmidi.mpe)
			mul[i] = (float)0x1fff / ceil(group->range); //MPE only supports whole semitone ranges
		else
			mul[i] = (float)0x1fff / group->range;
	}

	size = CONFIG_SUCCESS("is", uuid, path);
	CONFIG_SEND(size);

	if(config.oscmidi.mpe)
		oscmidi_init(); // send zones

	return 1;
}



@@ 443,7 517,12 @@ _oscmidi_range(const char *path, const char *fmt, uint_fast8_t argc, osc_data_t 
{
	OSC_MIDI_Group *grp = OSCMIDI_GID(path);

	return config_check_float(path, fmt, argc, buf, &grp->range);
	uint_fast8_t res = config_check_float(path, fmt, argc, buf, &grp->range);

	if( (argc > 1) && config.oscmidi.mpe)
		oscmidi_init(); // send zones

	return res;
}

static uint_fast8_t


@@ 497,6 576,7 @@ const OSC_Query_Item oscmidi_tree [] = {
	OSC_QUERY_ITEM_METHOD("enabled", "Enable/disable", _oscmidi_enabled, config_boolean_args),
	OSC_QUERY_ITEM_METHOD("multi", "OSC Multi argument?", _oscmidi_multi, config_boolean_args),
	OSC_QUERY_ITEM_METHOD("format", "OSC Format", _oscmidi_format, oscmidi_format_args),
	OSC_QUERY_ITEM_METHOD("mpe", "Multidimensional polyphonic expression?", _oscmidi_mpe, config_boolean_args),
	OSC_QUERY_ITEM_METHOD("path", "OSC Path", _oscmidi_path, oscmidi_path_args),
	OSC_QUERY_ITEM_METHOD("reset", "Reset attributes", _oscmidi_reset, NULL),
	OSC_QUERY_ITEM_ARRAY("attributes/", "Attributes", group_array, GROUP_MAX)

M sensors/sensors.c => sensors/sensors.c +11 -5
@@ 232,7 232,7 @@ _sensors_velocity_stiffness(const char *path, const char *fmt, uint_fast8_t argc
}

static uint_fast8_t
_group_clear(const char *path, const char *fmt, uint_fast8_t argc, osc_data_t *buf)
_group_reset(const char *path, const char *fmt, uint_fast8_t argc, osc_data_t *buf)
{
	(void)fmt;
	(void)argc;


@@ 242,7 242,7 @@ _group_clear(const char *path, const char *fmt, uint_fast8_t argc, osc_data_t *b

	buf_ptr = osc_get_int32(buf_ptr, &uuid);

	cmc_group_clear();
	cmc_group_reset();

	size = CONFIG_SUCCESS("is", uuid, path);
	CONFIG_SEND(size);


@@ 293,6 293,9 @@ _group_north(const char *path, const char *fmt, uint_fast8_t argc, osc_data_t *b
	else
		grp->pid &= ~CMC_NORTH;

	if(argc > 1)
		cmc_group_update(); // there may be new groups, we need to update

	return ret;
}



@@ 308,6 311,9 @@ _group_south(const char *path, const char *fmt, uint_fast8_t argc, osc_data_t *b
	else
		grp->pid &= ~CMC_SOUTH;

	if(argc > 1)
		cmc_group_update(); // there may be new groups, we need to update

	return ret;
}



@@ 337,7 343,7 @@ _group_number(const char *path, const char *fmt, uint_fast8_t argc, osc_data_t *

	buf_ptr = osc_get_int32(buf_ptr, &uuid);

	size = CONFIG_SUCCESS("isi", uuid, path, GROUP_MAX);
	size = CONFIG_SUCCESS("isi", uuid, path, cmc_groups_n);
	CONFIG_SEND(size);

	return 1;


@@ 348,7 354,7 @@ _group_number(const char *path, const char *fmt, uint_fast8_t argc, osc_data_t *
 */

static const OSC_Query_Argument group_number_args [] = {
	OSC_QUERY_ARGUMENT_INT32("Number", OSC_QUERY_MODE_R, GROUP_MAX, GROUP_MAX, 1)
	OSC_QUERY_ARGUMENT_INT32("Number", OSC_QUERY_MODE_R, 0, GROUP_MAX, 1)
};

static const OSC_Query_Argument min_args [] = {


@@ 384,7 390,7 @@ static const OSC_Query_Item group_attribute_array [] = {
};

static const OSC_Query_Item group_tree [] = {
	OSC_QUERY_ITEM_METHOD("reset", "Reset all groups", _group_clear, NULL),
	OSC_QUERY_ITEM_METHOD("reset", "Reset all groups", _group_reset, NULL),
	OSC_QUERY_ITEM_METHOD("number", "Number", _group_number, group_number_args),
	OSC_QUERY_ITEM_ARRAY("attributes/", "Attributes", group_attribute_array, GROUP_MAX)
};