2-midi-looping #3
@@ -29,6 +29,37 @@ void channel_add(jack_client_t *client, int idx) {
|
|||||||
cur[idx].loop_count = 0;
|
cur[idx].loop_count = 0;
|
||||||
cur[idx].record_pos = 0;
|
cur[idx].record_pos = 0;
|
||||||
cur[idx].playback_pos = 0;
|
cur[idx].playback_pos = 0;
|
||||||
|
cur[idx].type = CHANNEL_AUDIO;
|
||||||
|
|
||||||
|
next_channel_id++;
|
||||||
|
atomic_fetch_add(&channel_count, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void channel_add_midi(jack_client_t *client, int idx) {
|
||||||
|
struct channel_t *cur = get_channels_array();
|
||||||
|
|
||||||
|
char in_name[64], out_name[64];
|
||||||
|
snprintf(in_name, sizeof(in_name), "channel%d_midi_in", next_channel_id);
|
||||||
|
snprintf(out_name, sizeof(out_name), "channel%d_midi_out", next_channel_id);
|
||||||
|
|
||||||
|
cur[idx].midi_in = jack_port_register(
|
||||||
|
client, in_name, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
|
||||||
|
cur[idx].midi_out = jack_port_register(
|
||||||
|
client, out_name, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0);
|
||||||
|
if (!cur[idx].midi_in || !cur[idx].midi_out) {
|
||||||
|
fprintf(stderr, "Failed to register MIDI ports for channel %d\n",
|
||||||
|
next_channel_id);
|
||||||
|
atomic_store(&cur[idx].active, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_store(&cur[idx].active, 1);
|
||||||
|
atomic_store(&cur[idx].state, STATE_IDLE);
|
||||||
|
cur[idx].prev_state = -1;
|
||||||
|
cur[idx].loop_count = 0;
|
||||||
|
cur[idx].record_pos = 0;
|
||||||
|
cur[idx].playback_pos = 0;
|
||||||
|
cur[idx].type = CHANNEL_MIDI;
|
||||||
|
|
||||||
next_channel_id++;
|
next_channel_id++;
|
||||||
atomic_fetch_add(&channel_count, 1);
|
atomic_fetch_add(&channel_count, 1);
|
||||||
|
|||||||
@@ -7,6 +7,20 @@
|
|||||||
|
|
||||||
#define LOOP_BUF_SIZE (5 * 48000)
|
#define LOOP_BUF_SIZE (5 * 48000)
|
||||||
|
|
||||||
|
#define MAX_MIDI_EVENTS 1024
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CHANNEL_AUDIO,
|
||||||
|
CHANNEL_MIDI
|
||||||
|
} channel_type_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
jack_nframes_t timestamp; /* frame offset relative to loop start */
|
||||||
|
unsigned char status;
|
||||||
|
unsigned char note;
|
||||||
|
unsigned char velocity;
|
||||||
|
} midi_event_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
STATE_IDLE,
|
STATE_IDLE,
|
||||||
STATE_RECORD,
|
STATE_RECORD,
|
||||||
@@ -15,15 +29,22 @@ typedef enum {
|
|||||||
} looper_state;
|
} looper_state;
|
||||||
|
|
||||||
struct channel_t {
|
struct channel_t {
|
||||||
|
channel_type_t type; /* CHANNEL_AUDIO or CHANNEL_MIDI */
|
||||||
|
|
||||||
atomic_int state;
|
atomic_int state;
|
||||||
int prev_state;
|
int prev_state;
|
||||||
float loop_buffer[LOOP_BUF_SIZE];
|
union {
|
||||||
int loop_count;
|
float audio_buffer[LOOP_BUF_SIZE];
|
||||||
int record_pos;
|
midi_event_t midi_events[MAX_MIDI_EVENTS];
|
||||||
int playback_pos;
|
} loop;
|
||||||
|
int loop_count; /* for audio: length in samples; for MIDI: number of recorded events */
|
||||||
|
int record_pos; /* for audio: sample index; for MIDI: next event index for recording */
|
||||||
|
int playback_pos; /* for audio: sample index; for MIDI: next event index for playback */
|
||||||
atomic_int active;
|
atomic_int active;
|
||||||
jack_port_t *audio_in;
|
jack_port_t *audio_in;
|
||||||
jack_port_t *audio_out;
|
jack_port_t *audio_out;
|
||||||
|
jack_port_t *midi_in;
|
||||||
|
jack_port_t *midi_out;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Globals declared in looper.c */
|
/* Globals declared in looper.c */
|
||||||
@@ -39,5 +60,6 @@ static inline struct channel_t *get_channels_array(void) {
|
|||||||
|
|
||||||
void channel_add(jack_client_t *client, int idx);
|
void channel_add(jack_client_t *client, int idx);
|
||||||
void channel_remove(jack_client_t *client, int idx);
|
void channel_remove(jack_client_t *client, int idx);
|
||||||
|
void channel_add_midi(jack_client_t *client, int idx);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ typedef enum {
|
|||||||
CMD_UNBIND, // reset bind to channel 0
|
CMD_UNBIND, // reset bind to channel 0
|
||||||
CMD_ADD_CHANNEL, // add a new dynamic channel
|
CMD_ADD_CHANNEL, // add a new dynamic channel
|
||||||
CMD_REMOVE_CHANNEL, // remove last dynamic channel
|
CMD_REMOVE_CHANNEL, // remove last dynamic channel
|
||||||
|
CMD_ADD_MIDI_CHANNEL, // add a new dynamic MIDI channel
|
||||||
} cmd_type_t;
|
} cmd_type_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
179
src/looper.c
179
src/looper.c
@@ -175,49 +175,124 @@ int process_callback(jack_nframes_t nframes, void *arg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jack_nframes_t i;
|
if (active_channels[c].type == CHANNEL_MIDI) {
|
||||||
switch (state) {
|
/* MIDI channel handling */
|
||||||
case STATE_RECORD:
|
switch (state) {
|
||||||
if (in) {
|
case STATE_RECORD: {
|
||||||
float *f_out = (float *)out;
|
void *midi_in_buf = jack_port_get_buffer(active_channels[c].midi_in, nframes);
|
||||||
const float *f_in = (const float *)in;
|
if (midi_in_buf) {
|
||||||
for (i = 0; i < nframes; i++) {
|
jack_nframes_t nevents = jack_midi_get_event_count(midi_in_buf);
|
||||||
if (active_channels[c].record_pos < LOOP_BUF_SIZE)
|
jack_midi_event_t ev;
|
||||||
active_channels[c].loop_buffer[active_channels[c].record_pos++] =
|
for (jack_nframes_t j = 0; j < nevents; j++) {
|
||||||
f_in[i];
|
if (jack_midi_event_get(&ev, midi_in_buf, j) != 0) continue;
|
||||||
f_out[i] = f_in[i];
|
if (active_channels[c].record_pos < MAX_MIDI_EVENTS) {
|
||||||
|
active_channels[c].loop.midi_events[active_channels[c].record_pos].timestamp = ev.time;
|
||||||
|
active_channels[c].loop.midi_events[active_channels[c].record_pos].status = ev.buffer[0];
|
||||||
|
active_channels[c].loop.midi_events[active_channels[c].record_pos].note = (ev.size > 1) ? ev.buffer[1] : 0;
|
||||||
|
active_channels[c].loop.midi_events[active_channels[c].record_pos].velocity = (ev.size > 2) ? ev.buffer[2] : 0;
|
||||||
|
active_channels[c].record_pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* forward incoming MIDI to output during record */
|
||||||
|
void *midi_out_buf = jack_port_get_buffer(active_channels[c].midi_out, nframes);
|
||||||
|
if (midi_out_buf) {
|
||||||
|
jack_midi_clear_buffer(midi_out_buf);
|
||||||
|
for (jack_nframes_t j = 0; j < nevents; j++) {
|
||||||
|
if (jack_midi_event_get(&ev, midi_in_buf, j) != 0) continue;
|
||||||
|
jack_midi_event_write(midi_out_buf, ev.time, ev.buffer, ev.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
break;
|
||||||
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
|
|
||||||
}
|
}
|
||||||
break;
|
case STATE_LOOPING: {
|
||||||
|
void *midi_out_buf = jack_port_get_buffer(active_channels[c].midi_out, nframes);
|
||||||
case STATE_LOOPING:
|
if (midi_out_buf) {
|
||||||
if (active_channels[c].loop_count > 0) {
|
jack_midi_clear_buffer(midi_out_buf);
|
||||||
float *outf = (float *)out;
|
int cnt = active_channels[c].loop_count; /* number of recorded events */
|
||||||
for (i = 0; i < nframes; i++) {
|
if (cnt > 0) {
|
||||||
outf[i] =
|
/* simple: output all recorded events at frame 0 of each cycle */
|
||||||
active_channels[c].loop_buffer[active_channels[c].playback_pos];
|
for (int e = 0; e < cnt; e++) {
|
||||||
active_channels[c].playback_pos =
|
unsigned char msg[3];
|
||||||
(active_channels[c].playback_pos + 1) %
|
msg[0] = active_channels[c].loop.midi_events[e].status;
|
||||||
active_channels[c].loop_count;
|
msg[1] = active_channels[c].loop.midi_events[e].note;
|
||||||
|
msg[2] = active_channels[c].loop.midi_events[e].velocity;
|
||||||
|
jack_midi_event_write(midi_out_buf, 0, msg, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
break;
|
||||||
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
|
|
||||||
}
|
}
|
||||||
break;
|
case STATE_PAUSED:
|
||||||
|
/* no output */
|
||||||
case STATE_PAUSED:
|
break;
|
||||||
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
|
default: /* IDLE */
|
||||||
break;
|
/* pass through MIDI input to output */
|
||||||
|
{
|
||||||
default: /* IDLE */
|
void *midi_in_buf = jack_port_get_buffer(active_channels[c].midi_in, nframes);
|
||||||
if (in) {
|
void *midi_out_buf = jack_port_get_buffer(active_channels[c].midi_out, nframes);
|
||||||
memcpy(out, in, sizeof(jack_default_audio_sample_t) * nframes);
|
if (midi_in_buf && midi_out_buf) {
|
||||||
} else {
|
jack_midi_clear_buffer(midi_out_buf);
|
||||||
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
|
jack_nframes_t nevents = jack_midi_get_event_count(midi_in_buf);
|
||||||
|
jack_midi_event_t ev;
|
||||||
|
for (jack_nframes_t j = 0; j < nevents; j++) {
|
||||||
|
if (jack_midi_event_get(&ev, midi_in_buf, j) != 0) continue;
|
||||||
|
jack_midi_event_write(midi_out_buf, ev.time, ev.buffer, ev.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* for MIDI channels, the loop_count holds number of recorded events */
|
||||||
|
if (state == STATE_LOOPING) {
|
||||||
|
active_channels[c].loop_count = active_channels[c].record_pos;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* audio channel handling */
|
||||||
|
jack_nframes_t i;
|
||||||
|
switch (state) {
|
||||||
|
case STATE_RECORD:
|
||||||
|
if (in) {
|
||||||
|
float *f_out = (float *)out;
|
||||||
|
const float *f_in = (const float *)in;
|
||||||
|
for (i = 0; i < nframes; i++) {
|
||||||
|
if (active_channels[c].record_pos < LOOP_BUF_SIZE)
|
||||||
|
active_channels[c].loop.audio_buffer[active_channels[c].record_pos++] =
|
||||||
|
f_in[i];
|
||||||
|
f_out[i] = f_in[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STATE_LOOPING:
|
||||||
|
if (active_channels[c].loop_count > 0) {
|
||||||
|
float *outf = (float *)out;
|
||||||
|
for (i = 0; i < nframes; i++) {
|
||||||
|
outf[i] =
|
||||||
|
active_channels[c].loop.audio_buffer[active_channels[c].playback_pos];
|
||||||
|
active_channels[c].playback_pos =
|
||||||
|
(active_channels[c].playback_pos + 1) %
|
||||||
|
active_channels[c].loop_count;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STATE_PAUSED:
|
||||||
|
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: /* IDLE */
|
||||||
|
if (in) {
|
||||||
|
memcpy(out, in, sizeof(jack_default_audio_sample_t) * nframes);
|
||||||
|
} else {
|
||||||
|
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
active_channels[c].prev_state = state;
|
active_channels[c].prev_state = state;
|
||||||
@@ -341,6 +416,20 @@ void looper_process_commands(jack_client_t *client) {
|
|||||||
channel_add(client, idx);
|
channel_add(client, idx);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case CMD_ADD_MIDI_CHANNEL: {
|
||||||
|
int cap = atomic_load(&channel_capacity);
|
||||||
|
struct channel_t *cur = get_channels_array();
|
||||||
|
int idx;
|
||||||
|
for (idx = 0; idx < cap; idx++)
|
||||||
|
if (!atomic_load(&cur[idx].active))
|
||||||
|
break;
|
||||||
|
if (idx == cap) {
|
||||||
|
if (ensure_capacity(client, idx) != 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
channel_add_midi(client, idx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case CMD_REMOVE_CHANNEL: {
|
case CMD_REMOVE_CHANNEL: {
|
||||||
int cap = atomic_load(&channel_capacity);
|
int cap = atomic_load(&channel_capacity);
|
||||||
struct channel_t *cur = get_channels_array();
|
struct channel_t *cur = get_channels_array();
|
||||||
@@ -375,6 +464,20 @@ void looper_process_commands(jack_client_t *client) {
|
|||||||
channel_add(client, idx);
|
channel_add(client, idx);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case CMD_ADD_MIDI_CHANNEL: {
|
||||||
|
int cap = atomic_load(&channel_capacity);
|
||||||
|
struct channel_t *cur = get_channels_array();
|
||||||
|
int idx;
|
||||||
|
for (idx = 0; idx < cap; idx++)
|
||||||
|
if (!atomic_load(&cur[idx].active))
|
||||||
|
break;
|
||||||
|
if (idx == cap) {
|
||||||
|
if (ensure_capacity(client, idx) != 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
channel_add_midi(client, idx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case CMD_REMOVE_CHANNEL: {
|
case CMD_REMOVE_CHANNEL: {
|
||||||
int cap = atomic_load(&channel_capacity);
|
int cap = atomic_load(&channel_capacity);
|
||||||
struct channel_t *cur = get_channels_array();
|
struct channel_t *cur = get_channels_array();
|
||||||
|
|||||||
Reference in New Issue
Block a user