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.
309 lines
9.8 KiB
309 lines
9.8 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 "prefetch_pipe.h"
|
|
#include "tarfile.h"
|
|
#include "wavebank.h"
|
|
#include <assert.h>
|
|
#include <malloc.h>
|
|
#include <memory.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
// Don't bother fetching less than 4 (mono) or 8 KB (stereo)
|
|
|
|
void cbox_prefetch_pipe_init(struct cbox_prefetch_pipe *pipe, uint32_t buffer_size, uint32_t min_buffer_frames)
|
|
{
|
|
pipe->data = malloc(buffer_size);
|
|
pipe->buffer_size = buffer_size;
|
|
pipe->min_buffer_frames = min_buffer_frames;
|
|
pipe->sndfile = NULL;
|
|
pipe->state = pps_free;
|
|
}
|
|
|
|
gboolean cbox_prefetch_pipe_openfile(struct cbox_prefetch_pipe *pipe)
|
|
{
|
|
if (pipe->waveform->taritem)
|
|
pipe->sndfile = cbox_tarfile_opensndfile(pipe->waveform->tarfile, pipe->waveform->taritem, &pipe->sndstream, &pipe->info);
|
|
else
|
|
pipe->sndfile = sf_open(pipe->waveform->canonical_name, SFM_READ, &pipe->info);
|
|
if (!pipe->sndfile)
|
|
return FALSE;
|
|
pipe->file_pos_frame = sf_seek(pipe->sndfile, pipe->waveform->preloaded_frames, SEEK_SET);
|
|
if (pipe->file_loop_end > pipe->info.frames)
|
|
pipe->file_loop_end = pipe->info.frames;
|
|
pipe->buffer_loop_end = pipe->buffer_size / (sizeof(int16_t) * pipe->info.channels);
|
|
pipe->produced = pipe->file_pos_frame;
|
|
pipe->write_ptr = 0;
|
|
pipe->state = pps_active;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void cbox_prefetch_pipe_consumed(struct cbox_prefetch_pipe *pipe, uint32_t frames)
|
|
{
|
|
pipe->consumed += frames;
|
|
}
|
|
|
|
void cbox_prefetch_pipe_fetch(struct cbox_prefetch_pipe *pipe)
|
|
{
|
|
gboolean retry;
|
|
do {
|
|
retry = FALSE;
|
|
// XXXKF take consumption rate into account
|
|
|
|
// How many frames left to consume
|
|
int32_t supply = pipe->produced - pipe->consumed;
|
|
if (supply < 0)
|
|
{
|
|
// Overrun already occurred. Cut the losses by skipping already missed
|
|
// part.
|
|
uint32_t overrun = -supply;
|
|
|
|
// XXXKF This may or may not be stupid. I didn't put much thought into it.
|
|
pipe->produced += overrun;
|
|
pipe->file_pos_frame = sf_seek(pipe->sndfile, overrun, SEEK_CUR);
|
|
pipe->write_ptr += overrun;
|
|
if (pipe->write_ptr >= pipe->buffer_loop_end)
|
|
pipe->write_ptr %= pipe->buffer_loop_end;
|
|
}
|
|
//
|
|
if ((uint32_t)supply >= pipe->buffer_loop_end)
|
|
return;
|
|
|
|
// How many frames to read to fill the full prefetch size
|
|
int32_t readsize = pipe->buffer_loop_end - supply;
|
|
if (readsize < (int32_t)pipe->min_buffer_frames)
|
|
return;
|
|
|
|
if (pipe->write_ptr == pipe->buffer_loop_end)
|
|
pipe->write_ptr = 0;
|
|
|
|
// If reading across buffer boundary, only read the part up to buffer
|
|
// end, and then retry from start of the buffer.
|
|
if (pipe->write_ptr + readsize > pipe->buffer_loop_end)
|
|
{
|
|
readsize = pipe->buffer_loop_end - pipe->write_ptr;
|
|
retry = TRUE;
|
|
}
|
|
// If past the file loop end, restart at file loop start
|
|
if (pipe->file_pos_frame >= pipe->file_loop_end)
|
|
{
|
|
if (pipe->file_loop_start == (uint32_t)-1 || (pipe->loop_count && pipe->play_count >= pipe->loop_count - 1))
|
|
{
|
|
pipe->finished = TRUE;
|
|
for (int i = 0; i < readsize * pipe->info.channels; i++)
|
|
pipe->data[pipe->write_ptr * pipe->info.channels + i] = rand();
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
pipe->play_count++;
|
|
pipe->file_pos_frame = pipe->file_loop_start;
|
|
sf_seek(pipe->sndfile, pipe->file_loop_start, SEEK_SET);
|
|
}
|
|
}
|
|
// If reading across file loop boundary, read up to loop end and
|
|
// retry to restart
|
|
if (pipe->file_pos_frame + readsize > pipe->file_loop_end)
|
|
{
|
|
readsize = pipe->file_loop_end - pipe->file_pos_frame;
|
|
retry = TRUE;
|
|
}
|
|
|
|
int32_t actread = sf_readf_short(pipe->sndfile, pipe->data + pipe->write_ptr * pipe->info.channels, readsize);
|
|
pipe->produced += actread;
|
|
pipe->file_pos_frame += actread;
|
|
pipe->write_ptr += actread;
|
|
} while(retry);
|
|
}
|
|
|
|
void cbox_prefetch_pipe_closefile(struct cbox_prefetch_pipe *pipe)
|
|
{
|
|
assert(pipe->state == pps_closing);
|
|
assert(pipe->sndfile);
|
|
sf_close(pipe->sndfile);
|
|
pipe->sndfile = NULL;
|
|
pipe->state = pps_free;
|
|
}
|
|
|
|
void cbox_prefetch_pipe_close(struct cbox_prefetch_pipe *pipe)
|
|
{
|
|
if (pipe->sndfile)
|
|
cbox_prefetch_pipe_closefile(pipe);
|
|
if (pipe->data)
|
|
{
|
|
free(pipe->data);
|
|
pipe->data = NULL;
|
|
}
|
|
}
|
|
|
|
static void *prefetch_thread(void *user_data)
|
|
{
|
|
struct cbox_prefetch_stack *stack = user_data;
|
|
|
|
while(!stack->finished)
|
|
{
|
|
usleep(1000);
|
|
for (int i = 0; i < stack->pipe_count; i++)
|
|
{
|
|
struct cbox_prefetch_pipe *pipe = &stack->pipes[i];
|
|
switch(pipe->state)
|
|
{
|
|
case pps_free:
|
|
case pps_finished:
|
|
case pps_error:
|
|
break;
|
|
case pps_opening:
|
|
if (!cbox_prefetch_pipe_openfile(pipe))
|
|
pipe->state = pps_error;
|
|
assert(pipe->state != pps_opening);
|
|
break;
|
|
case pps_active:
|
|
if (pipe->returned)
|
|
pipe->state = pps_closing;
|
|
else
|
|
cbox_prefetch_pipe_fetch(pipe);
|
|
break;
|
|
case pps_closing:
|
|
cbox_prefetch_pipe_closefile(pipe);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct cbox_prefetch_stack *cbox_prefetch_stack_new(int npipes, uint32_t buffer_size, uint32_t min_buffer_frames)
|
|
{
|
|
struct cbox_prefetch_stack *stack = calloc(1, sizeof(struct cbox_prefetch_stack));
|
|
stack->pipes = calloc(npipes, sizeof(struct cbox_prefetch_pipe));
|
|
stack->next_free_pipe = calloc(npipes, sizeof(int));
|
|
|
|
for (int i = 0; i < npipes; i++)
|
|
{
|
|
cbox_prefetch_pipe_init(&stack->pipes[i], buffer_size, min_buffer_frames);
|
|
stack->next_free_pipe[i] = i - 1;
|
|
}
|
|
stack->pipe_count = npipes;
|
|
stack->last_free_pipe = npipes - 1;
|
|
stack->finished = FALSE;
|
|
|
|
if (pthread_create(&stack->thr_prefetch, NULL, prefetch_thread, stack))
|
|
{
|
|
// XXXKF set thread priority
|
|
g_warning("Cannot create a prefetch thread. Exiting.\n");
|
|
return NULL;
|
|
}
|
|
|
|
return stack;
|
|
}
|
|
|
|
struct cbox_prefetch_pipe *cbox_prefetch_stack_pop(struct cbox_prefetch_stack *stack, struct cbox_waveform *waveform, uint32_t file_loop_start, uint32_t file_loop_end, uint32_t loop_count)
|
|
{
|
|
// The stack may include some pipes that are already returned but not yet
|
|
// fully prepared for opening a new file
|
|
int *ppos = &stack->last_free_pipe;
|
|
while(*ppos != -1 && stack->pipes[*ppos].state != pps_free)
|
|
ppos = &stack->next_free_pipe[*ppos];
|
|
if (*ppos == -1) {
|
|
for (int i = 0; i < stack->pipe_count; ++i) {
|
|
printf("Pipe %d state %d next-free %d\n", i, stack->pipes[i].state, stack->next_free_pipe[i]);
|
|
}
|
|
printf("last_free_pipe %d\n", stack->last_free_pipe);
|
|
return NULL;
|
|
}
|
|
|
|
int pos = *ppos;
|
|
struct cbox_prefetch_pipe *pipe = &stack->pipes[pos];
|
|
|
|
*ppos = stack->next_free_pipe[pos];
|
|
stack->next_free_pipe[pos] = -1;
|
|
|
|
pipe->waveform = waveform;
|
|
if (file_loop_start == (uint32_t)-1 && loop_count)
|
|
file_loop_start = 0;
|
|
pipe->file_loop_start = file_loop_start;
|
|
pipe->file_loop_end = file_loop_end;
|
|
pipe->buffer_loop_end = 0;
|
|
pipe->finished = FALSE;
|
|
pipe->returned = FALSE;
|
|
pipe->produced = waveform->preloaded_frames;
|
|
pipe->consumed = 0;
|
|
pipe->play_count = 0;
|
|
pipe->loop_count = loop_count;
|
|
|
|
__sync_synchronize();
|
|
pipe->state = pps_opening;
|
|
return pipe;
|
|
}
|
|
|
|
void cbox_prefetch_stack_push(struct cbox_prefetch_stack *stack, struct cbox_prefetch_pipe *pipe)
|
|
{
|
|
switch(pipe->state)
|
|
{
|
|
case pps_free:
|
|
assert(0);
|
|
break;
|
|
case pps_error:
|
|
case pps_closed:
|
|
pipe->state = pps_free;
|
|
break;
|
|
case pps_opening:
|
|
// Close the file as soon as open operation completes
|
|
pipe->returned = TRUE;
|
|
break;
|
|
default:
|
|
pipe->state = pps_closing;
|
|
break;
|
|
}
|
|
|
|
__sync_synchronize();
|
|
|
|
int pos = pipe - stack->pipes;
|
|
assert(stack->next_free_pipe[pos] == -1);
|
|
stack->next_free_pipe[pos] = stack->last_free_pipe;
|
|
stack->last_free_pipe = pos;
|
|
|
|
__sync_synchronize();
|
|
}
|
|
|
|
int cbox_prefetch_stack_get_active_pipe_count(struct cbox_prefetch_stack *stack)
|
|
{
|
|
int count = 0;
|
|
for (int i = 0; i < stack->pipe_count; i++)
|
|
{
|
|
if (stack->pipes[i].state != pps_free)
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void cbox_prefetch_stack_destroy(struct cbox_prefetch_stack *stack)
|
|
{
|
|
void *result = NULL;
|
|
stack->finished = TRUE;
|
|
pthread_join(stack->thr_prefetch, &result);
|
|
for (int i = 0; i < stack->pipe_count; i++)
|
|
cbox_prefetch_pipe_close(&stack->pipes[i]);
|
|
free(stack->next_free_pipe);
|
|
free(stack->pipes);
|
|
free(stack);
|
|
}
|
|
|