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