You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

507 lines
15 KiB

/*
Calf Box, an open source musical instrument.
Copyright (C) 2010-2013 Krzysztof Foltman
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "dom.h"
#include "engine.h"
#include "io.h"
#include "midi.h"
#include "module.h"
#include "rt.h"
#include "stm.h"
#include "seq.h"
#include "song.h"
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
CBOX_CLASS_DEFINITION_ROOT(cbox_rt)
static void cbox_rt_process(void *user_data, struct cbox_io *io, uint32_t nframes);
struct cbox_rt_cmd_instance
{
struct cbox_rt_cmd_definition *definition;
void *user_data;
int *completed_ptr; // for synchronous commands only
};
static gboolean cbox_rt_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
{
struct cbox_rt *rt = ct->user_data;
if (!strcmp(cmd->command, "/status") && !strcmp(cmd->arg_types, ""))
{
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
if (rt->io)
{
GError *cerror = NULL;
if (cbox_io_get_disconnect_status(rt->io, &cerror))
{
return cbox_execute_on(fb, NULL, "/audio_channels", "ii", error, rt->io->io_env.input_count, rt->io->io_env.output_count) &&
cbox_execute_on(fb, NULL, "/state", "is", error, 1, "OK") &&
CBOX_OBJECT_DEFAULT_STATUS(rt, fb, error);
}
else
{
return cbox_execute_on(fb, NULL, "/audio_channels", "ii", error, rt->io->io_env.input_count, rt->io->io_env.output_count) &&
cbox_execute_on(fb, NULL, "/state", "is", error, -1, cerror ? cerror->message : "Unknown error") &&
CBOX_OBJECT_DEFAULT_STATUS(rt, fb, error);
}
}
else
return cbox_execute_on(fb, NULL, "/audio_channels", "ii", error, 0, 2) &&
cbox_execute_on(fb, NULL, "/state", "is", error, 0, "Offline") &&
CBOX_OBJECT_DEFAULT_STATUS(rt, fb, error);
}
else if (!strcmp(cmd->command, "/cycle") && !strcmp(cmd->arg_types, ""))
{
if (rt->io && !cbox_io_get_disconnect_status(rt->io, NULL))
{
return cbox_io_cycle(rt->io, fb, error);
}
else
{
if (rt->io)
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Already connected");
else
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Cannot cycle connection in off-line mode");
return FALSE;
}
}
else if (!strcmp(cmd->command, "/flush") && !cmd->arg_types[0]) {
cbox_rt_handle_cmd_queue(rt);
return TRUE;
}
else if (!strncmp(cmd->command, "/engine/", 8))
return cbox_execute_sub(&rt->engine->cmd_target, fb, cmd, cmd->command + 7, error);
else
return cbox_object_default_process_cmd(ct, fb, cmd, error);
}
struct cbox_rt *cbox_rt_new(struct cbox_document *doc)
{
struct cbox_rt *rt = malloc(sizeof(struct cbox_rt));
CBOX_OBJECT_HEADER_INIT(rt, cbox_rt, doc);
rt->rb_execute = cbox_fifo_new(sizeof(struct cbox_rt_cmd_instance) * RT_CMD_QUEUE_ITEMS);
rt->rb_cleanup = cbox_fifo_new(sizeof(struct cbox_rt_cmd_instance) * RT_CMD_QUEUE_ITEMS * 2);
rt->io = NULL;
rt->engine = NULL;
rt->started = FALSE;
rt->disconnected = FALSE;
rt->io_env.srate = 0;
rt->io_env.buffer_size = 0;
rt->io_env.input_count = 0;
rt->io_env.output_count = 0;
cbox_command_target_init(&rt->cmd_target, cbox_rt_process_cmd, rt);
CBOX_OBJECT_REGISTER(rt);
cbox_document_set_service(doc, "rt", &rt->_obj_hdr);
return rt;
}
struct cbox_objhdr *cbox_rt_newfunc(struct cbox_class *class_ptr, struct cbox_document *doc)
{
return NULL;
}
void cbox_rt_destroyfunc(struct cbox_objhdr *obj_ptr)
{
struct cbox_rt *rt = (void *)obj_ptr;
cbox_fifo_destroy(rt->rb_execute);
cbox_fifo_destroy(rt->rb_cleanup);
free(rt);
}
static void cbox_rt_on_disconnected(void *user_data)
{
struct cbox_rt *rt = user_data;
rt->disconnected = TRUE;
}
static void cbox_rt_on_reconnected(void *user_data)
{
struct cbox_rt *rt = user_data;
rt->disconnected = FALSE;
}
static void cbox_rt_on_midi_outputs_changed(void *user_data)
{
struct cbox_rt *rt = user_data;
if (rt->engine)
{
cbox_engine_update_song_playback(rt->engine);
cbox_engine_update_output_connections(rt->engine);
}
}
static void cbox_rt_on_midi_inputs_changed(void *user_data)
{
struct cbox_rt *rt = user_data;
if (rt->engine)
cbox_engine_update_input_connections(rt->engine);
}
static void cbox_rt_get_transport_data(void *user_data, gboolean explicit_pos, uint32_t time_samples, struct cbox_transport_position *tp)
{
struct cbox_rt *rt = user_data;
if (rt->engine)
{
struct cbox_master *master = rt->engine->master;
uint32_t time_ppqn;
if (explicit_pos)
time_ppqn = cbox_master_samples_to_ppqn(master, time_samples);
else
time_ppqn = master->spb ? master->spb->song_pos_ppqn : 0;
struct cbox_bbt bbt;
struct cbox_master_track_item mti;
cbox_master_ppqn_to_bbt(master, &bbt, time_ppqn, &mti);
tp->bar = bbt.bar;
tp->beat = bbt.beat;
tp->tick = bbt.tick;
tp->ticks_per_beat = master->ppqn_factor * 4 / mti.timesig_denom;
tp->bar_start_tick = 0;
tp->timesig_num = mti.timesig_num;
tp->timesig_denom = mti.timesig_denom;
tp->tempo = mti.tempo;
}
}
void cbox_rt_on_update_io_env(struct cbox_rt *rt)
{
if (rt->engine)
{
cbox_io_env_copy(&rt->engine->io_env, &rt->io_env);
cbox_master_set_sample_rate(rt->engine->master, rt->io_env.srate);
}
}
void cbox_rt_set_io(struct cbox_rt *rt, struct cbox_io *io)
{
assert(!rt->started);
rt->io = io;
if (io)
{
cbox_io_env_copy(&rt->io_env, &io->io_env);
cbox_rt_on_update_io_env(rt);
}
else
{
cbox_io_env_clear(&rt->io_env);
}
}
void cbox_rt_set_offline(struct cbox_rt *rt, int sample_rate, int buffer_size)
{
assert(!rt->started);
rt->io = NULL;
rt->io_env.srate = sample_rate;
rt->io_env.buffer_size = buffer_size;
rt->io_env.input_count = 0;
rt->io_env.output_count = 2;
cbox_rt_on_update_io_env(rt);
}
void cbox_rt_on_started(void *user_data)
{
struct cbox_rt *rt = user_data;
rt->started = 1;
}
void cbox_rt_on_stopped(void *user_data)
{
struct cbox_rt *rt = user_data;
rt->started = 0;
}
gboolean cbox_rt_on_transport_sync(void *user_data, enum cbox_transport_state state, uint32_t frame)
{
struct cbox_rt *rt = user_data;
if (!rt->engine)
return TRUE;
return cbox_engine_on_transport_sync(rt->engine, state, frame);
}
gboolean cbox_rt_on_tempo_sync(void *user_data, double tempo)
{
struct cbox_rt *rt = user_data;
if (rt->engine)
cbox_engine_on_tempo_sync(rt->engine, tempo);
return TRUE;
}
void cbox_rt_start(struct cbox_rt *rt, struct cbox_command_target *fb)
{
if (rt->io)
{
rt->cbs = calloc(1, sizeof(struct cbox_io_callbacks));
rt->cbs->user_data = rt;
rt->cbs->process = cbox_rt_process;
rt->cbs->on_started = cbox_rt_on_started;
rt->cbs->on_stopped = cbox_rt_on_stopped;
rt->cbs->on_disconnected = cbox_rt_on_disconnected;
rt->cbs->on_reconnected = cbox_rt_on_reconnected;
rt->cbs->on_midi_inputs_changed = cbox_rt_on_midi_inputs_changed;
rt->cbs->on_midi_outputs_changed = cbox_rt_on_midi_outputs_changed;
rt->cbs->on_transport_sync = cbox_rt_on_transport_sync;
rt->cbs->on_tempo_sync = cbox_rt_on_tempo_sync;
rt->cbs->get_transport_data = cbox_rt_get_transport_data;
assert(!rt->started);
cbox_io_start(rt->io, rt->cbs, fb);
assert(rt->started);
}
}
void cbox_rt_stop(struct cbox_rt *rt)
{
if (rt->io)
{
assert(rt->started);
cbox_io_stop(rt->io);
free(rt->cbs);
rt->cbs = NULL;
assert(!rt->started);
}
}
void cbox_rt_handle_cmd_queue(struct cbox_rt *rt)
{
struct cbox_rt_cmd_instance cmd;
while(cbox_fifo_read_atomic(rt->rb_cleanup, &cmd, sizeof(cmd)))
{
assert(!cmd.completed_ptr);
cmd.definition->cleanup(cmd.user_data);
}
}
static void wait_write_space(struct cbox_fifo *rb)
{
int t = 0;
while (cbox_fifo_writespace(rb) < sizeof(struct cbox_rt_cmd_instance))
{
// wait until some space frees up in the execute queue
usleep(1000);
t++;
if (t >= 1000)
{
fprintf(stderr, "Execute queue full, waiting...\n");
t = 0;
}
}
}
void cbox_rt_execute_cmd_sync(struct cbox_rt *rt, struct cbox_rt_cmd_definition *def, void *user_data)
{
struct cbox_rt_cmd_instance cmd;
if (def->prepare)
if (def->prepare(user_data))
return;
// No realtime thread - do it all in the main thread
if (!rt || !rt->started || rt->disconnected)
{
while (!def->execute(user_data))
;
if (def->cleanup)
def->cleanup(user_data);
return;
}
int completed = 0;
memset(&cmd, 0, sizeof(cmd));
cmd.definition = def;
cmd.user_data = user_data;
cmd.completed_ptr = &completed;
wait_write_space(rt->rb_execute);
cbox_fifo_write_atomic(rt->rb_execute, &cmd, sizeof(cmd));
do
{
if (completed)
{
if (def->cleanup)
def->cleanup(user_data);
break;
}
struct cbox_rt_cmd_instance cmd2;
if (!cbox_fifo_read_atomic(rt->rb_cleanup, &cmd2, sizeof(cmd2)))
{
// still no result in cleanup queue - wait
usleep(10000);
continue;
}
// async command or something from outer layer - clean it up
cmd2.definition->cleanup(cmd2.user_data);
} while(1);
}
int cbox_rt_execute_cmd_async(struct cbox_rt *rt, struct cbox_rt_cmd_definition *def, void *user_data)
{
struct cbox_rt_cmd_instance cmd = { def, user_data, NULL };
if (def->prepare)
{
int error = def->prepare(user_data);
if (error)
return error;
}
// No realtime thread - do it all in the main thread
if (!rt || !rt->started || rt->disconnected)
{
while (!def->execute(user_data))
;
if (def->cleanup)
def->cleanup(user_data);
} else {
wait_write_space(rt->rb_execute);
cbox_fifo_write_atomic(rt->rb_execute, &cmd, sizeof(cmd));
// will be cleaned up by next sync call or by cbox_rt_cmd_handle_queue
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////
void cbox_rt_process(void *user_data, struct cbox_io *io, uint32_t nframes)
{
struct cbox_rt *rt = user_data;
if (rt->engine)
cbox_engine_process(rt->engine, io, nframes, io->output_buffers, io->io_env.output_count);
else
cbox_rt_handle_rt_commands(rt);
}
////////////////////////////////////////////////////////////////////////////////////////
void cbox_rt_handle_rt_commands(struct cbox_rt *rt)
{
struct cbox_rt_cmd_instance cmd;
// Process command queue, needs engine's MIDI aux buf to be initialised to work
int cost = 0;
while(cost < RT_MAX_COST_PER_CALL && cbox_fifo_peek(rt->rb_execute, &cmd, sizeof(cmd)))
{
int result = (cmd.definition->execute)(cmd.user_data);
if (!result)
break;
cost += result;
cbox_fifo_consume(rt->rb_execute, sizeof(cmd));
if (cmd.completed_ptr)
*cmd.completed_ptr = 1;
else if (cmd.definition->cleanup)
{
gboolean success = cbox_fifo_write_atomic(rt->rb_cleanup, (const char *)&cmd, sizeof(cmd));
if (!success)
g_error("Clean-up FIFO full. Main thread deadlock?");
}
}
}
////////////////////////////////////////////////////////////////////////////////////////
#define cbox_rt_swap_pointers_args(ARG) ARG(void **, ptr) ARG(void *, new_value)
DEFINE_RT_FUNC(void *, cbox_rt, rt, cbox_rt_swap_pointers)
{
void *old_value = *ptr;
*ptr = new_value;
return old_value;
}
#define cbox_rt_swap_pointers_into_args(ARG) ARG(void **, ptr) ARG(void *, new_value) ARG(void **, old_value_p)
DEFINE_RT_VOID_FUNC(cbox_rt, rt, cbox_rt_swap_pointers_into)
{
*old_value_p = *ptr;
*ptr = new_value;
}
#define cbox_rt_swap_pointers_and_update_count_args(ARG) ARG(void **, ptr) ARG(void *, new_value) ARG(uint32_t *, pcount) ARG(uint32_t, new_count)
DEFINE_RT_FUNC(void *, cbox_rt, rt, cbox_rt_swap_pointers_and_update_count)
{
void *old_value = *ptr;
*ptr = new_value;
if (pcount)
*pcount = new_count;
return old_value;
}
////////////////////////////////////////////////////////////////////////////////////////
void cbox_rt_array_insert(struct cbox_rt *rt, void ***ptr, uint32_t *pcount, int index, void *new_value)
{
assert(index >= -1);
assert(index == -1 || (uint32_t)index <= *pcount);
assert(*pcount < (1U << 31));
void **new_array = stm_array_clone_insert(*ptr, *pcount, index, new_value);
free(cbox_rt_swap_pointers_and_update_count(rt, (void **)ptr, new_array, pcount, *pcount + 1));
}
void *cbox_rt_array_remove(struct cbox_rt *rt, void ***ptr, uint32_t *pcount, int index)
{
if (index == -1)
index = *pcount - 1;
assert(index >= 0);
assert((uint32_t)index < *pcount);
assert(*pcount < (1U << 31));
void *p = (*ptr)[index];
void **new_array = stm_array_clone_remove(*ptr, *pcount, index);
free(cbox_rt_swap_pointers_and_update_count(rt, (void **)ptr, new_array, pcount, *pcount - 1));
return p;
}
gboolean cbox_rt_array_remove_by_value(struct cbox_rt *rt, void ***ptr, uint32_t *pcount, void *value_to_remove)
{
for (uint32_t i = 0; i < *pcount; i++)
{
if ((*ptr)[i] == value_to_remove)
{
cbox_rt_array_remove(rt, ptr, pcount, i);
return TRUE;
}
}
return FALSE;
}
////////////////////////////////////////////////////////////////////////////////////////
struct cbox_midi_merger *cbox_rt_get_midi_output(struct cbox_rt *rt, struct cbox_uuid *uuid)
{
if (rt->engine)
{
struct cbox_midi_merger *merger = cbox_engine_get_midi_output(rt->engine, uuid);
if (merger)
return merger;
}
if (!rt->io)
return NULL;
struct cbox_midi_output *midiout = cbox_io_get_midi_output(rt->io, NULL, uuid);
return midiout ? &midiout->merger : NULL;
}
////////////////////////////////////////////////////////////////////////////////////////