@@ -13,7 +13,8 @@
# include <string.h>
/* Global state (shared across files) */
struct channel_t channels [ MAX_CHANNELS ] ;
_Atomic struct channel_t * channels = NULL ;
atomic_int channel_capacity = 0 ;
atomic_int channel_count = 0 ;
int next_channel_id = 1 ;
spsc_queue_t cmd_queue_main_midi ;
@@ -29,13 +30,38 @@ spsc_queue_t cmd_queue;
static int pending_unregister_idx = - 1 ;
static int pending_unregister_cycle = 0 ;
/* Helper: grow the channel array so that index idx is valid */
static int ensure_capacity ( jack_client_t * client , int idx ) {
( void ) client ;
int cur_cap = atomic_load ( & channel_capacity ) ;
if ( idx < cur_cap )
return 0 ;
int new_cap = cur_cap = = 0 ? 8 : cur_cap ;
while ( new_cap < = idx )
new_cap * = 2 ;
struct channel_t * new_arr = calloc ( new_cap , sizeof ( struct channel_t ) ) ;
if ( ! new_arr )
return - 1 ;
/* copy existing channels */
if ( cur_cap > 0 )
memcpy ( new_arr , atomic_load ( & channels ) , cur_cap * sizeof ( struct channel_t ) ) ;
/* atomically publish new array */
struct channel_t * old = atomic_exchange ( & channels , new_arr ) ;
atomic_store ( & channel_capacity , new_cap ) ;
free ( old ) ;
return 0 ;
}
static void apply_command ( command_t cmd ) {
struct channel_t * cur = get_channels_array ( ) ;
int cap = atomic_load ( & channel_capacity ) ;
switch ( cmd . type ) {
case CMD_CYCLE :
if ( cmd . channel > = 0 & & cmd . channel < MAX_CHANNELS ) {
int cur = atomic_load ( & channels [ cmd . channel ] . state ) ;
if ( cmd . channel > = 0 & & cmd . channel < cap ) {
int cst = atomic_load ( & cur [ cmd . channel ] . state ) ;
int next ;
switch ( cur ) {
switch ( cst ) {
case STATE_IDLE :
next = STATE_RECORD ;
break ;
@@ -52,15 +78,15 @@ static void apply_command(command_t cmd) {
next = STATE_IDLE ;
break ;
}
atomic_store ( & channels [ cmd . channel ] . state , next ) ;
atomic_store ( & cur [ cmd . channel ] . state , next ) ;
}
break ;
case CMD_STOP :
if ( cmd . channel > = 0 & & cmd . channel < MAX_CHANNELS )
atomic_store ( & channels [ cmd . channel ] . state , STATE_IDLE ) ;
if ( cmd . channel > = 0 & & cmd . channel < cap )
atomic_store ( & cur [ cmd . channel ] . state , STATE_IDLE ) ;
else {
for ( int i = 0 ; i < MAX_CHANNELS ; i + + )
atomic_store ( & channels [ i ] . state , STATE_IDLE ) ;
for ( int i = 0 ; i < cap ; i + + )
atomic_store ( & cur [ i ] . state , STATE_IDLE ) ;
}
break ;
case CMD_BIND_CHANNEL :
@@ -94,37 +120,39 @@ int process_callback(jack_nframes_t nframes, void *arg) {
}
/* process each active channel */
for ( int c = 0 ; c < MAX_CHANNELS ; c + + ) {
if ( ! atomic_load ( & channels [ c ] . active ) )
struct channel_t * active_channels = get_channels_array ( ) ;
int cap = atomic_load ( & channel_capacity ) ;
for ( int c = 0 ; c < cap ; c + + ) {
if ( ! atomic_load ( & active_channels [ c ] . active ) )
continue ;
/* Guard against NULL ports (e.g. if port registration failed) */
if ( ! channels [ c ] . audio_in | | ! channels [ c ] . audio_out ) {
if ( ! active_ channels[ c ] . audio_in | | ! active_ channels[ c ] . audio_out ) {
fprintf ( stderr , " WARN: channel %d has NULL audio port(s), skipping \n " , c ) ;
continue ;
}
const jack_default_audio_sample_t * in =
( const jack_default_audio_sample_t * ) jack_port_get_buffer (
channels [ c ] . audio_in , nframes ) ;
active_ channels[ c ] . audio_in , nframes ) ;
jack_default_audio_sample_t * out =
( jack_default_audio_sample_t * ) jack_port_get_buffer (
channels [ c ] . audio_out , nframes ) ;
active_ channels[ c ] . audio_out , nframes ) ;
if ( ! out )
continue ;
int state = atomic_load ( & channels [ c ] . state ) ;
int state = atomic_load ( & active_ channels[ c ] . state ) ;
if ( state ! = channels [ c ] . prev_state ) {
if ( state ! = active_ channels[ c ] . prev_state ) {
switch ( state ) {
case STATE_RECORD :
channels [ c ] . record_pos = 0 ;
channels [ c ] . loop_count = 0 ;
active_ channels[ c ] . record_pos = 0 ;
active_ channels[ c ] . loop_count = 0 ;
break ;
case STATE_LOOPING :
if ( channels [ c ] . record_pos > 0 )
channels [ c ] . loop_count = channels [ c ] . record_pos ;
channels [ c ] . playback_pos = 0 ;
if ( active_ channels[ c ] . record_pos > 0 )
active_ channels[ c ] . loop_count = active_ channels[ c ] . record_pos ;
active_ channels[ c ] . playback_pos = 0 ;
break ;
default :
break ;
@@ -138,8 +166,8 @@ int process_callback(jack_nframes_t nframes, void *arg) {
float * f_out = ( float * ) out ;
const float * f_in = ( const float * ) in ;
for ( i = 0 ; i < nframes ; i + + ) {
if ( channels [ c ] . record_pos < LOOP_BUF_SIZE )
channels [ c ] . loop_buffer [ channels [ c ] . record_pos + + ] = f_in [ i ] ;
if ( active_ channels[ c ] . record_pos < LOOP_BUF_SIZE )
active_ channels[ c ] . loop_buffer [ active_ channels[ c ] . record_pos + + ] = f_in [ i ] ;
f_out [ i ] = f_in [ i ] ;
}
} else {
@@ -148,12 +176,12 @@ int process_callback(jack_nframes_t nframes, void *arg) {
break ;
case STATE_LOOPING :
if ( channels [ c ] . loop_count > 0 ) {
if ( active_ channels[ c ] . loop_count > 0 ) {
float * outf = ( float * ) out ;
for ( i = 0 ; i < nframes ; i + + ) {
outf [ i ] = channels [ c ] . loop_buffer [ channels [ c ] . playback_pos ] ;
channels [ c ] . playback_pos =
( channels [ c ] . playback_pos + 1 ) % channels [ c ] . loop_count ;
outf [ i ] = active_ channels[ c ] . loop_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 ) ;
@@ -173,7 +201,7 @@ int process_callback(jack_nframes_t nframes, void *arg) {
break ;
}
channels [ c ] . prev_state = state ;
active_ channels[ c ] . prev_state = state ;
}
/* MIDI clock events – affect channel 0 only */
@@ -189,18 +217,22 @@ int process_callback(jack_nframes_t nframes, void *arg) {
unsigned char msg = cev . buffer [ 0 ] ;
switch ( msg ) {
case 0xFA : {
int s = atomic_load ( & channels [ 0 ] . state );
struct channel_t * cur = atomic_load ( & channels ) ;
int s = atomic_load ( & cur [ 0 ] . state ) ;
if ( s = = STATE_IDLE )
atomic_store ( & channels [ 0 ] . state , STATE_RECORD ) ;
atomic_store ( & cur [ 0 ] . state , STATE_RECORD ) ;
break ;
}
case 0xFC :
atomic_store ( & channels [ 0 ] . state , STATE_IDLE );
case 0xFC : {
struct channel_t * cur = atomic_load ( & channels ) ;
atomic_store ( & cur [ 0 ] . state , STATE_IDLE ) ;
break ;
}
case 0xFB : {
int s = atomic_load ( & channels [ 0 ] . state );
struct channel_t * cur = atomic_load ( & channels ) ;
int s = atomic_load ( & cur [ 0 ] . state ) ;
if ( s = = STATE_PAUSED )
atomic_store ( & channels [ 0 ] . state , STATE_LOOPING ) ;
atomic_store ( & cur [ 0 ] . state , STATE_LOOPING ) ;
break ;
}
default :
@@ -231,23 +263,30 @@ int looper_init(jack_client_t *client) {
queue_init ( & cmd_queue ) ;
queue_init ( & cmd_queue_main_midi ) ;
queue_init ( & cmd_queue_main_fifo ) ;
/* channel 0 */
channels [ 0 ] . active = 1 ;
atomic_store ( & channels [ 0 ] . state , STATE_IDLE ) ;
channels [ 0 ] . prev_state = - 1 ;
channels [ 0 ] . loop_count = 0 ;
channels [ 0 ] . record_pos = 0 ;
channels [ 0 ] . playback_pos = 0 ;
channels [ 0 ] . audio_in = jack_port_register (
/* allocate initial array for at least one channel */
if ( ensure_capacity ( client , 0 ) ! = 0 ) {
fprintf ( stderr , " Cannot allocate channel array \n " ) ;
return - 1 ;
}
struct channel_t * init = atomic_load ( & channels ) ;
/* channel 0 */
init [ 0 ] . active = 1 ;
atomic_store ( & init [ 0 ] . state , STATE_IDLE ) ;
init [ 0 ] . prev_state = - 1 ;
init [ 0 ] . loop_count = 0 ;
init [ 0 ] . record_pos = 0 ;
init [ 0 ] . playback_pos = 0 ;
init [ 0 ] . audio_in = jack_port_register (
client , " input " , JACK_DEFAULT_AUDIO_TYPE , JackPortIsInput , 0 ) ;
channels [ 0 ] . audio_out = jack_port_register (
init [ 0 ] . audio_out = jack_port_register (
client , " output " , JACK_DEFAULT_AUDIO_TYPE , JackPortIsOutput , 0 ) ;
if ( ! channels [ 0 ] . audio_in | | ! channels [ 0 ] . audio_out ) {
if ( ! init [ 0 ] . audio_in | | ! init [ 0 ] . audio_out ) {
fprintf ( stderr , " Could not create audio ports for channel 0 \n " ) ;
return - 1 ;
}
channel_count = 1 ;
atomic_store ( & channel_count , 1 ) ;
midi_control_port = jack_port_register (
client , " control " , JACK_DEFAULT_MIDI_TYPE , JackPortIsInput , 0 ) ;
@@ -270,18 +309,25 @@ void looper_process_commands(jack_client_t *client) {
while ( queue_pop ( & cmd_queue_main_midi , & cmd ) ) {
switch ( cmd . type ) {
case CMD_ADD_CHANNEL : {
int cap = atomic_load ( & channel_capacity ) ;
struct channel_t * cur = get_channels_array ( ) ;
int idx ;
for ( idx = 0 ; idx < MAX_CHANNELS ; idx + + )
if ( ! channels [ idx ] . active )
for ( idx = 0 ; idx < cap ; idx + + )
if ( ! atomic_load ( & cur [ idx ] . active ) )
break ;
if ( idx < MAX_CHANNELS )
if ( idx = = cap ) {
if ( ensure_capacity ( client , idx ) ! = 0 )
break ;
}
channel_add ( client , idx ) ;
break ;
}
case CMD_REMOVE_CHANNEL : {
int cap = atomic_load ( & channel_capacity ) ;
struct channel_t * cur = get_channels_array ( ) ;
int remove_idx = - 1 ;
for ( int idx = 1 ; idx < MAX_CHANNELS ; idx + + )
if ( channels [ idx ] . active )
for ( int idx = 1 ; idx < cap ; idx + + )
if ( atomic_load ( & cur [ idx ] . active ) )
remove_idx = idx ;
if ( remove_idx ! = - 1 ) {
channel_remove ( client , remove_idx ) ;
@@ -297,18 +343,25 @@ void looper_process_commands(jack_client_t *client) {
while ( queue_pop ( & cmd_queue_main_fifo , & cmd ) ) {
switch ( cmd . type ) {
case CMD_ADD_CHANNEL : {
int cap = atomic_load ( & channel_capacity ) ;
struct channel_t * cur = get_channels_array ( ) ;
int idx ;
for ( idx = 0 ; idx < MAX_CHANNELS ; idx + + )
if ( ! channels [ idx ] . active )
for ( idx = 0 ; idx < cap ; idx + + )
if ( ! atomic_load ( & cur [ idx ] . active ) )
break ;
if ( idx < MAX_CHANNELS )
if ( idx = = cap ) {
if ( ensure_capacity ( client , idx ) ! = 0 )
break ;
}
channel_add ( client , idx ) ;
break ;
}
case CMD_REMOVE_CHANNEL : {
int cap = atomic_load ( & channel_capacity ) ;
struct channel_t * cur = get_channels_array ( ) ;
int remove_idx = - 1 ;
for ( int idx = 1 ; idx < MAX_CHANNELS ; idx + + )
if ( channels [ idx ] . active )
for ( int idx = 1 ; idx < cap ; idx + + )
if ( atomic_load ( & cur [ idx ] . active ) )
remove_idx = idx ;
if ( remove_idx ! = - 1 ) {
channel_remove ( client , remove_idx ) ;
@@ -327,10 +380,11 @@ void looper_process_commands(jack_client_t *client) {
int current_cycle = atomic_load ( & global_rt_cycles ) ;
if ( current_cycle - pending_unregister_cycle > = 1 ) {
int idx = pending_unregister_idx ;
if ( channels [ idx ] . audio_in )
jack_port_unregister ( client , channels [ idx ] . audio_in ) ;
if ( channels [ idx ] . audio_out )
jack_port_unregister ( client , channels [ idx ] . audio_out ) ;
struct channel_t * cur = atomic_load ( & channels ) ;
if ( cur [ idx ] . audio_in )
jack_port_unregister ( client , cur [ idx ] . audio_in ) ;
if ( cur [ idx ] . audio_out )
jack_port_unregister ( client , cur [ idx ] . audio_out ) ;
pending_unregister_idx = - 1 ;
}
}