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.
 
 

734 lines
22 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 "assert.h"
#include "config.h"
#include "config-api.h"
#include "dspmath.h"
#include "fifo.h"
#include "module.h"
#include "rt.h"
#include <errno.h>
#include <glib.h>
#include <malloc.h>
#include <math.h>
#include <memory.h>
#include <pthread.h>
#include <sndfile.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define CBOX_STREAM_PLAYER_ERROR cbox_stream_player_error_quark()
enum CboxStreamPlayerError
{
CBOX_STREAM_PLAYER_ERROR_FAILED,
};
GQuark cbox_stream_player_error_quark(void)
{
return g_quark_from_string("cbox-stream-player-error-quark");
}
#define CUE_BUFFER_SIZE 16000
#define PREFETCH_THRESHOLD ((uint32_t)(CUE_BUFFER_SIZE / 4))
#define MAX_READAHEAD_BUFFERS 3
#define NO_SAMPLE_LOOP ((uint64_t)-1ULL)
struct stream_player_cue_point
{
volatile uint64_t position;
volatile uint32_t size, length;
float *data;
int queued;
};
enum stream_state_phase
{
STOPPED,
PLAYING,
STOPPING,
STARTING
};
struct stream_state
{
SNDFILE *sndfile;
SF_INFO info;
uint64_t readptr;
uint64_t restart;
uint64_t readptr_new;
volatile int buffer_in_use;
struct stream_player_cue_point cp_start, cp_loop, cp_readahead[MAX_READAHEAD_BUFFERS];
int cp_readahead_ready[MAX_READAHEAD_BUFFERS];
struct stream_player_cue_point *pcp_current, *pcp_next;
struct cbox_fifo *rb_for_reading, *rb_just_read;
float gain, fade_gain, fade_increment;
enum stream_state_phase phase;
pthread_t thr_preload;
int thread_started;
gchar *filename;
};
struct stream_player_module
{
struct cbox_module module;
struct stream_state *stream;
float fade_increment;
};
static void init_cue(struct stream_state *ss, struct stream_player_cue_point *pt, uint32_t size, uint64_t pos)
{
pt->data = malloc(size * sizeof(float) * ss->info.channels);
pt->size = size;
pt->length = 0;
pt->queued = 0;
pt->position = pos;
}
static void destroy_cue(struct stream_player_cue_point *pt)
{
free(pt->data);
pt->data = NULL;
}
static void load_at_cue(struct stream_state *ss, struct stream_player_cue_point *pt)
{
if (pt->position != NO_SAMPLE_LOOP)
{
sf_seek(ss->sndfile, pt->position, 0);
pt->length = sf_readf_float(ss->sndfile, pt->data, pt->size);
}
pt->queued = 0;
}
static int is_contained(struct stream_player_cue_point *pt, uint64_t ofs)
{
return pt->position != NO_SAMPLE_LOOP && ofs >= pt->position && ofs < pt->position + pt->length;
}
static int is_queued(struct stream_player_cue_point *pt, uint64_t ofs)
{
return pt->queued && pt->position != NO_SAMPLE_LOOP && ofs >= pt->position && ofs < pt->position + pt->size;
}
struct stream_player_cue_point *get_cue(struct stream_state *ss, uint64_t pos)
{
int i;
if (is_contained(&ss->cp_loop, pos))
return &ss->cp_loop;
if (is_contained(&ss->cp_start, pos))
return &ss->cp_start;
for (i = 0; i < MAX_READAHEAD_BUFFERS; i++)
{
if (ss->cp_readahead_ready[i] && is_contained(&ss->cp_readahead[i], pos))
return &ss->cp_readahead[i];
}
return NULL;
}
struct stream_player_cue_point *get_queued_buffer(struct stream_state *ss, uint64_t pos)
{
int i;
for (i = 0; i < MAX_READAHEAD_BUFFERS; i++)
{
if (!ss->cp_readahead_ready[i] && is_queued(&ss->cp_readahead[i], pos))
return &ss->cp_readahead[i];
}
return NULL;
}
void request_load(struct stream_state *ss, int buf_idx, uint64_t pos)
{
unsigned char cidx = (unsigned char)buf_idx;
struct stream_player_cue_point *pt = &ss->cp_readahead[buf_idx];
ss->cp_readahead_ready[buf_idx] = 0;
pt->position = pos;
pt->length = 0;
pt->queued = 1;
#ifdef NDEBUG
cbox_fifo_write_atomic(ss->rb_for_reading, &cidx, 1);
#else
gboolean result = cbox_fifo_write_atomic(ss->rb_for_reading, &cidx, 1);
assert(result);
#endif
}
int get_unused_buffer(struct stream_state *ss)
{
int i = 0;
int notbad = -1;
// return first buffer that is not currently played or in queue; XXXKF this is a very primitive strategy, a good one would at least use the current play position
for (i = 0; i < MAX_READAHEAD_BUFFERS; i++)
{
int64_t rel;
if (&ss->cp_readahead[i] == ss->pcp_current)
continue;
if (ss->cp_readahead[i].queued)
continue;
// If there's any unused buffer, return it
if (ss->cp_readahead[i].position == NO_SAMPLE_LOOP)
return i;
// If this has already been played, return it
rel = ss->readptr - ss->cp_readahead[i].position;
if (rel >= ss->cp_readahead[i].length)
return i;
// Use as second chance
notbad = i;
}
return notbad;
}
static void *sample_preload_thread(void *user_data)
{
struct stream_state *ss = user_data;
do {
unsigned char buf_idx;
if (!cbox_fifo_read_atomic(ss->rb_for_reading, &buf_idx, 1))
{
usleep(5000);
continue;
}
if (buf_idx == 255)
break;
// fprintf(stderr, "Preload: %d, %lld\n", (int)buf_idx, (long long)m->cp_readahead[buf_idx].position);
load_at_cue(ss, &ss->cp_readahead[buf_idx]);
// fprintf(stderr, "Preloaded\n", (int)buf_idx, (long long)m->cp_readahead[buf_idx].position);
cbox_fifo_write_atomic(ss->rb_just_read, &buf_idx, 1);
} while(1);
return NULL;
}
void stream_player_process_event(struct cbox_module *module, const uint8_t *data, uint32_t len)
{
// struct stream_player_module *m = (struct stream_player_module *)module;
}
static void request_next(struct stream_state *ss, uint64_t pos)
{
// Check if we've requested a next buffer, if not, request it
// First verify if our idea of 'next' buffer is correct
// XXXKF This is technically incorrect, it won't tell whether the next "block" that's there
// isn't actually a single sample. I worked it around by ensuring end of blocks are always
// at CUE_BUFFER_SIZE boundary, and this works well, but causes buffers to be of uneven size.
if (ss->pcp_next && (is_contained(ss->pcp_next, pos) || is_queued(ss->pcp_next, pos)))
{
// We're still waiting for the requested buffer, but that's OK
return;
}
// We don't know the next buffer, or the next buffer doesn't contain
// the sample we're looking for.
ss->pcp_next = get_queued_buffer(ss, pos);
if (!ss->pcp_next)
{
// It hasn't even been requested - request it
int buf_idx = get_unused_buffer(ss);
if(buf_idx == -1)
{
printf("Ran out of buffers\n");
return;
}
request_load(ss, buf_idx, pos);
ss->pcp_next = &ss->cp_readahead[buf_idx];
// printf("@%lld: Requested load into buffer %d at %lld\n", (long long)m->readptr, buf_idx, (long long) pos);
}
}
static void copy_samples(struct stream_state *ss, cbox_sample_t **outputs, float *data, int count, int ofs, int pos)
{
int i;
float gain = ss->gain * ss->fade_gain;
if (ss->phase == STARTING)
{
ss->fade_gain += ss->fade_increment;
if (ss->fade_gain >= 1)
{
ss->fade_gain = 1;
ss->phase = PLAYING;
}
}
else
if (ss->phase == STOPPING)
{
ss->fade_gain -= ss->fade_increment;
if (ss->fade_gain < 0)
{
ss->fade_gain = 0;
ss->phase = STOPPED;
}
}
float new_gain = ss->gain * ss->fade_gain;
float gain_delta = (new_gain - gain) * (1.0 / CBOX_BLOCK_SIZE);
if (ss->info.channels == 1)
{
for (i = 0; i < count; i++)
{
outputs[0][ofs + i] = outputs[1][ofs + i] = gain * data[pos + i];
gain += gain_delta;
}
}
else
if (ss->info.channels == 2)
{
for (i = 0; i < count; i++)
{
outputs[0][ofs + i] = gain * data[pos << 1];
outputs[1][ofs + i] = gain * data[(pos << 1) + 1];
gain += gain_delta;
pos++;
}
}
else
{
uint32_t ch = ss->info.channels;
for (i = 0; i < count; i++)
{
outputs[0][ofs + i] = gain * data[pos * ch];
outputs[1][ofs + i] = gain * data[pos * ch + 1];
gain += gain_delta;
pos++;
}
}
ss->readptr += count;
if (ss->readptr >= (uint32_t)ss->info.frames)
{
ss->readptr = ss->restart;
}
}
void stream_player_process_block(struct cbox_module *module, cbox_sample_t **inputs, cbox_sample_t **outputs)
{
struct stream_player_module *m = (struct stream_player_module *)module;
struct stream_state *ss = m->stream;
int i, optr;
unsigned char buf_idx;
if (!ss || ss->readptr == NO_SAMPLE_LOOP)
{
for (int i = 0; i < CBOX_BLOCK_SIZE; i++)
{
outputs[0][i] = outputs[1][i] = 0;
}
return;
}
// receive buffer completion messages from the queue
while(cbox_fifo_read_atomic(ss->rb_just_read, &buf_idx, 1))
{
ss->cp_readahead_ready[buf_idx] = 1;
}
optr = 0;
do {
if (ss->phase == STOPPED)
break;
if (ss->readptr == NO_SAMPLE_LOOP)
{
ss->phase = STOPPED;
break;
}
if (ss->pcp_current && !is_contained(ss->pcp_current, ss->readptr))
ss->pcp_current = NULL;
if (!ss->pcp_current)
{
if (ss->pcp_next && is_contained(ss->pcp_next, ss->readptr))
{
ss->pcp_current = ss->pcp_next;
ss->pcp_next = NULL;
}
else
ss->pcp_current = get_cue(ss, ss->readptr);
}
if (!ss->pcp_current)
{
printf("Underrun at %d\n", (int)ss->readptr);
// Underrun; request/wait for next block and output zeros
request_next(ss, ss->readptr);
break;
}
assert(!ss->pcp_current->queued);
uint64_t data_end = ss->pcp_current->position + ss->pcp_current->length;
uint32_t data_left = data_end - ss->readptr;
// If we're close to running out of space, prefetch the next bit
if (data_left < PREFETCH_THRESHOLD && data_end < (uint64_t)ss->info.frames)
request_next(ss, data_end);
float *data = ss->pcp_current->data;
uint32_t pos = ss->readptr - ss->pcp_current->position;
uint32_t count = data_end - ss->readptr;
if (count > (uint32_t)(CBOX_BLOCK_SIZE - optr))
count = (uint32_t)(CBOX_BLOCK_SIZE - optr);
// printf("Copy samples: copying %d, optr %d, %lld = %d @ [%lld - %lld], left %d\n", count, optr, (long long)m->readptr, pos, (long long)m->pcp_current->position, (long long)data_end, (int)data_left);
copy_samples(ss, outputs, data, count, optr, pos);
optr += count;
} while(optr < CBOX_BLOCK_SIZE);
for (i = optr; i < CBOX_BLOCK_SIZE; i++)
{
outputs[0][i] = outputs[1][i] = 0;
}
}
static void stream_state_destroy(struct stream_state *ss)
{
unsigned char cmd = 255;
if (ss->rb_for_reading && ss->thread_started)
{
cbox_fifo_write_atomic(ss->rb_for_reading, &cmd, 1);
pthread_join(ss->thr_preload, NULL);
}
destroy_cue(&ss->cp_start);
destroy_cue(&ss->cp_loop);
for (int i = 0; i < MAX_READAHEAD_BUFFERS; i++)
destroy_cue(&ss->cp_readahead[i]);
if (ss->rb_for_reading)
cbox_fifo_destroy(ss->rb_for_reading);
if (ss->rb_just_read)
cbox_fifo_destroy(ss->rb_just_read);
if (ss->sndfile)
sf_close(ss->sndfile);
if (ss->filename)
g_free(ss->filename);
free(ss);
}
void stream_player_destroyfunc(struct cbox_module *module)
{
struct stream_player_module *m = (struct stream_player_module *)module;
if (m->stream)
stream_state_destroy(m->stream);
}
static struct stream_state *stream_state_new(const char *context, const gchar *filename, uint64_t loop, float fade_increment, GError **error)
{
struct stream_state *stream = malloc(sizeof(struct stream_state));
memset(&stream->info, 0, sizeof(stream->info));
stream->sndfile = sf_open(filename, SFM_READ, &stream->info);
if (!stream->sndfile)
{
g_set_error(error, CBOX_STREAM_PLAYER_ERROR, CBOX_STREAM_PLAYER_ERROR_FAILED, "instrument '%s': cannot open file '%s': %s", context, filename, sf_strerror(NULL));
free(stream);
return NULL;
}
// g_message("Frames %d channels %d", (int)stream->info.frames, (int)stream->info.channels);
stream->rb_for_reading = cbox_fifo_new(MAX_READAHEAD_BUFFERS + 1);
stream->rb_just_read = cbox_fifo_new(MAX_READAHEAD_BUFFERS + 1);
stream->phase = STOPPED;
stream->readptr = 0;
stream->restart = loop;
stream->pcp_current = &stream->cp_start;
stream->pcp_next = NULL;
stream->gain = 1.0;
stream->fade_gain = 0.0;
stream->fade_increment = fade_increment;
stream->thread_started = 0;
stream->filename = g_strdup(filename);
init_cue(stream, &stream->cp_start, CUE_BUFFER_SIZE, 0);
load_at_cue(stream, &stream->cp_start);
if (stream->restart > 0 && (stream->restart % CUE_BUFFER_SIZE) > 0)
init_cue(stream, &stream->cp_loop, CUE_BUFFER_SIZE + (CUE_BUFFER_SIZE - (stream->restart % CUE_BUFFER_SIZE)), stream->restart);
else
init_cue(stream, &stream->cp_loop, CUE_BUFFER_SIZE, stream->restart);
load_at_cue(stream, &stream->cp_loop);
for (int i = 0; i < MAX_READAHEAD_BUFFERS; i++)
{
init_cue(stream, &stream->cp_readahead[i], CUE_BUFFER_SIZE, NO_SAMPLE_LOOP);
stream->cp_readahead_ready[i] = 0;
}
if (pthread_create(&stream->thr_preload, NULL, sample_preload_thread, stream))
{
stream_state_destroy(stream);
g_set_error(error, CBOX_STREAM_PLAYER_ERROR, CBOX_STREAM_PLAYER_ERROR_FAILED, "cannot create streaming thread: %s", strerror(errno));
return NULL;
}
stream->thread_started = 1;
return stream;
}
///////////////////////////////////////////////////////////////////////////////////
static int stream_player_seek_execute(void *p)
{
struct stream_player_module *m = p;
m->stream->readptr = m->stream->readptr_new;
return 1;
}
static struct cbox_rt_cmd_definition stream_seek_command = {
.prepare = NULL,
.execute = stream_player_seek_execute,
.cleanup = NULL
};
///////////////////////////////////////////////////////////////////////////////////
static int stream_player_play_execute(void *p)
{
struct stream_player_module *m = p;
if (m->stream->readptr == NO_SAMPLE_LOOP)
m->stream->readptr = 0;
if (m->stream->phase != PLAYING)
{
if (m->stream->readptr == 0)
{
m->stream->fade_gain = 1.0;
m->stream->phase = PLAYING;
}
else
m->stream->phase = STARTING;
}
return 1;
}
static struct cbox_rt_cmd_definition stream_play_command = {
.prepare = NULL,
.execute = stream_player_play_execute,
.cleanup = NULL
};
///////////////////////////////////////////////////////////////////////////////////
static int stream_player_stop_execute(void *p)
{
struct stream_player_module *m = p;
if (m->stream->phase != STOPPED)
m->stream->phase = STOPPING;
return 1;
}
static struct cbox_rt_cmd_definition stream_stop_command = {
.prepare = NULL,
.execute = stream_player_stop_execute,
.cleanup = NULL
};
///////////////////////////////////////////////////////////////////////////////////
struct load_command_data
{
struct stream_player_module *module;
gchar *context;
gchar *filename;
int loop_start;
struct stream_state *stream, *old_stream;
GError **error;
};
static int stream_player_load_prepare(void *p)
{
struct load_command_data *c = p;
if (!c->filename)
return 0;
c->stream = stream_state_new(c->context, c->filename, c->loop_start, c->module->fade_increment, c->error);
c->old_stream = NULL;
if (!c->stream)
{
g_free(c->filename);
return -1;
}
return 0;
}
static int stream_player_load_execute(void *p)
{
struct load_command_data *c = p;
c->old_stream = c->module->stream;
c->module->stream = c->stream;
return 1;
}
static void stream_player_load_cleanup(void *p)
{
struct load_command_data *c = p;
if (c->filename)
g_free(c->filename);
if (c->old_stream && c->old_stream != c->stream)
stream_state_destroy(c->old_stream);
}
static struct cbox_rt_cmd_definition stream_load_command = {
.prepare = stream_player_load_prepare,
.execute = stream_player_load_execute,
.cleanup = stream_player_load_cleanup
};
///////////////////////////////////////////////////////////////////////////////////
static gboolean require_stream(struct stream_player_module *m, GError **error)
{
if (!m->stream)
{
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "No stream loaded");
return FALSE;
}
return TRUE;
}
gboolean stream_player_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
{
struct stream_player_module *m = (struct stream_player_module *)ct->user_data;
if (!strcmp(cmd->command, "/seek") && !strcmp(cmd->arg_types, "i"))
{
if (!require_stream(m, error))
return FALSE;
m->stream->readptr_new = CBOX_ARG_I(cmd, 0);
cbox_rt_execute_cmd_async(m->module.rt, &stream_seek_command, m);
}
else if (!strcmp(cmd->command, "/play") && !strcmp(cmd->arg_types, ""))
{
if (!require_stream(m, error))
return FALSE;
cbox_rt_execute_cmd_async(m->module.rt, &stream_play_command, m);
}
else if (!strcmp(cmd->command, "/stop") && !strcmp(cmd->arg_types, ""))
{
if (!require_stream(m, error))
return FALSE;
cbox_rt_execute_cmd_async(m->module.rt, &stream_stop_command, m);
}
else if (!strcmp(cmd->command, "/status") && !strcmp(cmd->arg_types, ""))
{
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
if (m->stream)
{
return cbox_execute_on(fb, NULL, "/filename", "s", error, m->stream->filename) &&
cbox_execute_on(fb, NULL, "/pos", "i", error, m->stream->readptr) &&
cbox_execute_on(fb, NULL, "/length", "i", error, m->stream->info.frames) &&
cbox_execute_on(fb, NULL, "/channels", "i", error, m->stream->info.channels) &&
cbox_execute_on(fb, NULL, "/sample_rate", "i", error, m->stream->info.samplerate) &&
cbox_execute_on(fb, NULL, "/playing", "i", error, m->stream->phase != STOPPED);
}
else
return
cbox_execute_on(fb, NULL, "/filename", "s", error, "");
}
else if (!strcmp(cmd->command, "/load") && !strcmp(cmd->arg_types, "si"))
{
struct load_command_data *c = malloc(sizeof(struct load_command_data));
c->context = m->module.instance_name;
c->module = m;
c->stream = NULL;
c->old_stream = NULL;
c->filename = g_strdup(CBOX_ARG_S(cmd, 0));
c->loop_start = CBOX_ARG_I(cmd, 1);
c->error = error;
cbox_rt_execute_cmd_sync(m->module.rt, &stream_load_command, c);
gboolean success = c->stream != NULL;
free(c);
return success;
}
else if (!strcmp(cmd->command, "/unload") && !strcmp(cmd->arg_types, ""))
{
struct load_command_data *c = malloc(sizeof(struct load_command_data));
c->context = m->module.instance_name;
c->module = m;
c->stream = NULL;
c->old_stream = NULL;
c->filename = NULL;
c->loop_start = 0;
c->error = error;
cbox_rt_execute_cmd_sync(m->module.rt, &stream_load_command, c);
gboolean success = c->stream == NULL;
free(c);
return success;
}
else
{
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Unknown command '%s'", cmd->command);
return FALSE;
}
return TRUE;
}
MODULE_CREATE_FUNCTION(stream_player)
{
static int inited = 0;
if (!inited)
{
inited = 1;
}
struct stream_player_module *m = malloc(sizeof(struct stream_player_module));
gchar *filename = cbox_config_get_string(cfg_section, "file");
CALL_MODULE_INIT(m, 0, 2, stream_player);
m->module.process_event = stream_player_process_event;
m->module.process_block = stream_player_process_block;
m->fade_increment = 1.0 / (cbox_config_get_float(cfg_section, "fade_time", 0.01) * (m->module.srate / CBOX_BLOCK_SIZE));
if (filename)
{
m->stream = stream_state_new(cfg_section, filename, (uint64_t)(int64_t)cbox_config_get_int(cfg_section, "loop", -1), m->fade_increment, error);
if (!m->stream)
{
CBOX_DELETE(&m->module);
return NULL;
}
}
else
m->stream = NULL;
return &m->module;
}
struct cbox_module_keyrange_metadata stream_player_keyranges[] = {
};
struct cbox_module_livecontroller_metadata stream_player_controllers[] = {
};
DEFINE_MODULE(stream_player, 0, 2)