@@ -39,7 +39,8 @@ protected: | |||||
const char *_filename; | const char *_filename; | ||||
nframes_t _length; /* length of file in samples */ | |||||
nframes_t _length; /* length of file in samples */ | |||||
nframes_t _samplerate; /* sample rate */ | |||||
int _channels; | int _channels; | ||||
Peaks *_peaks; | Peaks *_peaks; | ||||
@@ -58,7 +59,7 @@ public: | |||||
const char *name ( void ) const { return _filename; } | const char *name ( void ) const { return _filename; } | ||||
nframes_t length ( void ) const { return _length; } | nframes_t length ( void ) const { return _length; } | ||||
int channels ( void ) const { return _channels; } | int channels ( void ) const { return _channels; } | ||||
nframes_t samplerate ( void ) const { return _samplerate; } | |||||
// Peaks const * peaks ( void ) { return &_peaks; } | // Peaks const * peaks ( void ) { return &_peaks; } | ||||
virtual bool open ( void ) = 0; | virtual bool open ( void ) = 0; | ||||
@@ -66,6 +67,7 @@ public: | |||||
virtual void seek ( nframes_t offset ) = 0; | virtual void seek ( nframes_t offset ) = 0; | ||||
virtual nframes_t read ( sample_t *buf, int channel, nframes_t len ) = 0; | virtual nframes_t read ( sample_t *buf, int channel, nframes_t len ) = 0; | ||||
virtual nframes_t read ( sample_t *buf, int channel, nframes_t start, nframes_t end ) = 0; | virtual nframes_t read ( sample_t *buf, int channel, nframes_t start, nframes_t end ) = 0; | ||||
virtual nframes_t write ( sample_t *buf, nframes_t len ) = 0; | |||||
bool read_peaks( float fpp, nframes_t start, nframes_t end, int *peaks, Peak **pbuf, int *channels ); | bool read_peaks( float fpp, nframes_t start, nframes_t end, int *peaks, Peak **pbuf, int *channels ); | ||||
@@ -33,7 +33,6 @@ Audio_File_SF::from_file ( const char *filename ) | |||||
SNDFILE *in; | SNDFILE *in; | ||||
SF_INFO si; | SF_INFO si; | ||||
Audio_File_SF *c = NULL; | Audio_File_SF *c = NULL; | ||||
memset( &si, 0, sizeof( si ) ); | memset( &si, 0, sizeof( si ) ); | ||||
@@ -53,9 +52,10 @@ Audio_File_SF::from_file ( const char *filename ) | |||||
c = new Audio_File_SF; | c = new Audio_File_SF; | ||||
c->_current_read = 0; | c->_current_read = 0; | ||||
c->_filename = strdup( filename ); | |||||
c->_length = si.frames; | |||||
c->_channels = si.channels; | |||||
c->_filename = strdup( filename ); | |||||
c->_length = si.frames; | |||||
c->_samplerate = si.samplerate; | |||||
c->_channels = si.channels; | |||||
c->_in = in; | c->_in = in; | ||||
// sf_close( in ); | // sf_close( in ); | ||||
@@ -68,6 +68,38 @@ invalid: | |||||
return NULL; | return NULL; | ||||
} | } | ||||
Audio_File_SF * | |||||
Audio_File_SF::create ( const char *filename, nframes_t samplerate, int channels, const char *format ) | |||||
{ | |||||
SF_INFO si; | |||||
SNDFILE *out; | |||||
memset( &si, 0, sizeof( si ) ); | |||||
si.samplerate = samplerate; | |||||
si.channels = channels; | |||||
/* FIXME: bogus */ | |||||
si.format = SF_FORMAT_WAV | SF_FORMAT_PCM_24 | SF_ENDIAN_CPU; | |||||
if ( ! ( out = sf_open( filename, SFM_WRITE, &si ) ) ) | |||||
{ | |||||
printf( "couldn't create soundfile.\n" ); | |||||
return NULL; | |||||
} | |||||
Audio_File_SF *c = new Audio_File_SF; | |||||
c->_filename = strdup( filename ); | |||||
c->_length = 0; | |||||
c->_samplerate = samplerate; | |||||
c->_channels = channels; | |||||
c->_in = out; | |||||
return c; | |||||
} | |||||
bool | bool | ||||
Audio_File_SF::open ( void ) | Audio_File_SF::open ( void ) | ||||
{ | { | ||||
@@ -146,3 +178,12 @@ Audio_File_SF::read ( sample_t *buf, int channel, nframes_t start, nframes_t end | |||||
return len; | return len; | ||||
} | } | ||||
/** write /nframes/ from /buf/ to soundfile. Should be interleaved for | |||||
* the appropriate number of channels */ | |||||
nframes_t | |||||
Audio_File_SF::write ( sample_t *buf, nframes_t nframes ) | |||||
{ | |||||
return sf_writef_float( _in, buf, nframes ); | |||||
} |
@@ -34,11 +34,13 @@ class Audio_File_SF : public Audio_File | |||||
public: | public: | ||||
static Audio_File_SF *from_file ( const char *filename ); | static Audio_File_SF *from_file ( const char *filename ); | ||||
static Audio_File_SF *create ( const char *filename, nframes_t samplerate, int channels, const char *format ); | |||||
bool open ( void ); | bool open ( void ); | ||||
void close ( void ); | void close ( void ); | ||||
void seek ( nframes_t offset ); | void seek ( nframes_t offset ); | ||||
nframes_t read ( sample_t *buf, int channel, nframes_t len ); | nframes_t read ( sample_t *buf, int channel, nframes_t len ); | ||||
nframes_t read ( sample_t *buf, int channel, nframes_t start, nframes_t end ); | nframes_t read ( sample_t *buf, int channel, nframes_t start, nframes_t end ); | ||||
nframes_t write ( sample_t *buf, nframes_t nframes ); | |||||
}; | }; |
@@ -68,15 +68,12 @@ Disk_Stream::Disk_Stream ( Track_Header *th, float frame_rate, nframes_t nframes | |||||
size_t bufsize = _total_blocks * nframes * sizeof( sample_t ); | size_t bufsize = _total_blocks * nframes * sizeof( sample_t ); | ||||
/* const int blocks = 64; */ | |||||
/* const size_t bufsize = (blocks * (nframes * sizeof( sample_t ))) + sizeof( sample_t ); */ | |||||
for ( int i = channels; i--; ) | for ( int i = channels; i--; ) | ||||
_rb.push_back( jack_ringbuffer_create( bufsize ) ); | _rb.push_back( jack_ringbuffer_create( bufsize ) ); | ||||
sem_init( &_blocks, 0, _total_blocks ); | sem_init( &_blocks, 0, _total_blocks ); | ||||
run(); | |||||
// run(); | |||||
} | } | ||||
Disk_Stream::~Disk_Stream ( ) | Disk_Stream::~Disk_Stream ( ) | ||||
@@ -109,7 +106,7 @@ Disk_Stream::track ( void ) | |||||
void | void | ||||
Disk_Stream::run ( void ) | Disk_Stream::run ( void ) | ||||
{ | { | ||||
if ( pthread_create( &_thread, NULL, &Disk_Stream::io_thread, this ) != 0 ) | |||||
if ( pthread_create( &_thread, NULL, &Disk_Stream::disk_thread, this ) != 0 ) | |||||
/* error */; | /* error */; | ||||
} | } | ||||
@@ -121,47 +118,6 @@ Disk_Stream::resize ( nframes_t nframes ) | |||||
/* FIXME: to something here! */; | /* FIXME: to something here! */; | ||||
} | } | ||||
bool | |||||
Disk_Stream::seek_pending ( void ) | |||||
{ | |||||
return _pending_seek != (nframes_t)-1; | |||||
} | |||||
/* THREAD: RT */ | |||||
/** request that the IO thread perform a seek and rebuffer. This is | |||||
called for each Disk_Stream whenever the RT thread determines that | |||||
the transport has jumped to a new position. This is called *before* | |||||
process. */ | |||||
void | |||||
Disk_Stream::seek ( nframes_t frame ) | |||||
{ | |||||
printf( "requesting seek\n" ); | |||||
if ( seek_pending() ) | |||||
printf( "seek error, attempt to seek while seek is pending\n" ); | |||||
_pending_seek = frame; | |||||
/* flush buffers */ | |||||
for ( int i = channels(); i--; ) | |||||
jack_ringbuffer_read_advance( _rb[ i ], jack_ringbuffer_read_space( _rb[ i ] ) ); | |||||
/* dirty hack... reset the semaphore. Should we just call sem_init | |||||
* again instead? */ | |||||
/* sem_init( &_blocks, 0, _total_blocks ); */ | |||||
int n; | |||||
sem_getvalue( &_blocks, &n ); | |||||
n = _total_blocks - n; | |||||
while ( n-- ) | |||||
sem_post( &_blocks ); | |||||
} | |||||
/* void */ | /* void */ | ||||
/* DIsk_Stream::shutdown ( void ) */ | /* DIsk_Stream::shutdown ( void ) */ | ||||
@@ -171,45 +127,15 @@ Disk_Stream::seek ( nframes_t frame ) | |||||
/* static wrapper */ | /* static wrapper */ | ||||
void * | void * | ||||
Disk_Stream::io_thread ( void *arg ) | |||||
Disk_Stream::disk_thread ( void *arg ) | |||||
{ | { | ||||
((Disk_Stream*)arg)->io_thread(); | |||||
((Disk_Stream*)arg)->disk_thread(); | |||||
return NULL; | return NULL; | ||||
} | } | ||||
/* THREAD: IO */ | |||||
/** read /nframes/ from the attached track into /buf/ */ | |||||
void | |||||
Disk_Stream::read_block ( sample_t *buf, nframes_t nframes ) | |||||
{ | |||||
memset( buf, 0, nframes * sizeof( sample_t ) * channels() ); | |||||
/* stupid chicken/egg */ | |||||
if ( ! timeline ) | |||||
return; | |||||
// printf( "IO: attempting to read block @ %lu\n", _frame ); | |||||
if ( ! track() ) | |||||
{ | |||||
// _frame += _nframes; | |||||
return; | |||||
} | |||||
timeline->rdlock(); | |||||
if ( track()->play( buf, _frame, nframes, channels() ) ) | |||||
_frame += nframes; | |||||
else | |||||
/* error */; | |||||
timeline->unlock(); | |||||
} | |||||
int | int | ||||
Disk_Stream::output_buffer_percent ( void ) | |||||
Disk_Stream::buffer_percent ( void ) | |||||
{ | { | ||||
int n; | int n; | ||||
@@ -217,128 +143,3 @@ Disk_Stream::output_buffer_percent ( void ) | |||||
return 100 - (n * 100 / _total_blocks); | return 100 - (n * 100 / _total_blocks); | ||||
} | } | ||||
/* THREAD: IO */ | |||||
void | |||||
Disk_Stream::io_thread ( void ) | |||||
{ | |||||
printf( "IO thread running...\n" ); | |||||
/* buffer to hold the interleaved data returned by the track reader */ | |||||
sample_t *buf = new sample_t[ _nframes * channels() ]; | |||||
const size_t block_size = _nframes * sizeof( sample_t ); | |||||
while ( wait_for_block() ) | |||||
{ | |||||
// printf( "IO: RT thread is ready for more data...\n" ); | |||||
// printf( "IO: disk buffer is %3d%% full\r", output_buffer_percent() ); | |||||
// lock(); // for seeking | |||||
if ( seek_pending() ) | |||||
{ | |||||
printf( "performing seek\n" ); | |||||
_frame = _pending_seek; | |||||
_pending_seek = -1; | |||||
/* finish flushing the buffer */ | |||||
/* for ( int i = channels(); i-- ) */ | |||||
/* jack_ringbuffer_write_advance( _rb[ i ], jack_ringbuffer_write_space( _rb[ i ] ) ); */ | |||||
} | |||||
/* FIXME: should we not read from disk in larger-than-JACK-buffer blocks? */ | |||||
read_block( buf, _nframes ); | |||||
// unlock(); // for seeking | |||||
/* deinterleave the buffer and stuff it into the per-channel ringbuffers */ | |||||
for ( int i = channels(); i--; ) | |||||
{ | |||||
while ( jack_ringbuffer_write_space( _rb[ i ] ) < block_size ) | |||||
{ | |||||
printf( "IO: disk buffer overrun!\n" ); | |||||
/* FIXME: is this *really* the right thing to do? */ | |||||
usleep( 2000 ); | |||||
} | |||||
/* deinterleave direcectly into the ringbuffer to avoid | |||||
* unnecessary copying */ | |||||
jack_ringbuffer_data_t rbd[2]; | |||||
jack_ringbuffer_get_write_vector( _rb[ i ], rbd ); | |||||
if ( rbd[ 0 ].len >= _nframes ) | |||||
/* it'll all fit in one go */ | |||||
buffer_deinterleave_one_channel( (sample_t*)rbd[ 0 ].buf, buf, i, channels(), _nframes ); | |||||
else if ( rbd[ 1 ].len ) | |||||
{ | |||||
/* there's enough space in the ringbuffer, but it's not contiguous */ | |||||
/* do the first half */ | |||||
const nframes_t f = rbd[ 1 ].len / sizeof( sample_t ); | |||||
buffer_deinterleave_one_channel( (sample_t*)rbd[ 0 ].buf, buf, i, channels(), f ); | |||||
assert( rbd[ 1 ].len >= (_nframes - f) * sizeof( sample_t ) ); | |||||
/* do the second half */ | |||||
buffer_deinterleave_one_channel( (sample_t*)rbd[ 1 ].buf, buf + f, i, channels(), _nframes - f ); | |||||
} | |||||
else | |||||
printf( "programming error: expected more space in ringbuffer\n" ); | |||||
/* buffer_deinterleave_one_channel( (sample_t*)rbd.buf, buf, i, channels(), _nframes ); */ | |||||
/* jack_ringbuffer_write( _rb[ i ], (char*)cbuf, block_size ); */ | |||||
jack_ringbuffer_write_advance( _rb[ i ], _nframes * sizeof( sample_t ) ); | |||||
} | |||||
} | |||||
printf( "IO thread terminating.\n" ); | |||||
delete[] buf; | |||||
} | |||||
/* THREAD: RT */ | |||||
/** take a single block from the ringbuffers and send it out the | |||||
* attached track's ports */ | |||||
nframes_t | |||||
Disk_Stream::process ( nframes_t nframes ) | |||||
{ | |||||
const size_t block_size = nframes * sizeof( sample_t ); | |||||
// printf( "process: %lu %lu %lu\n", _frame, _frame + nframes, nframes ); | |||||
for ( int i = channels(); i--; ) | |||||
{ | |||||
void *buf = _th->output[ i ].buffer( nframes ); | |||||
if ( jack_ringbuffer_read( _rb[ i ], (char*)buf, block_size ) < block_size ) | |||||
{ | |||||
printf( "RT: buffer underrun (disk can't keep up).\n" ); | |||||
memset( buf, 0, block_size ); | |||||
/* FIXME: we need to resync somehow */ | |||||
} | |||||
/* /\* testing. *\/ */ | |||||
/* FILE *fp = fopen( "testing.au", "a" ); */ | |||||
/* fwrite( buf, block_size, 1, fp ); */ | |||||
/* fclose( fp ); */ | |||||
} | |||||
block_processed(); | |||||
/* FIXME: bogus */ | |||||
return nframes; | |||||
} |
@@ -19,7 +19,7 @@ | |||||
#pragma once | #pragma once | ||||
#include "Port.H" // for nframes_t | |||||
#include "types.h" | |||||
#include <jack/ringbuffer.h> | #include <jack/ringbuffer.h> | ||||
#include <semaphore.h> | #include <semaphore.h> | ||||
@@ -38,30 +38,30 @@ class Audio_Track; | |||||
class Disk_Stream : public Mutex | class Disk_Stream : public Mutex | ||||
{ | { | ||||
pthread_t _thread; | |||||
protected: | |||||
Track_Header *_th; /* Track_Header we whould be playing */ | |||||
pthread_t _thread; /* io thread */ | |||||
nframes_t _nframes; | |||||
nframes_t _frame; | |||||
Track_Header *_th; /* Track_Header we belong to */ | |||||
vector <jack_ringbuffer_t *> _rb; | |||||
nframes_t _nframes; /* buffer size */ | |||||
// jack_ringbuffer_t *_rb; /* One interleaved ringbuffer for all channels */ | |||||
nframes_t _frame; /* location of disk read */ | |||||
sem_t _blocks; /* semaphore to wake the IO thread with */ | |||||
vector < jack_ringbuffer_t * >_rb; /* one ringbuffer for each channel */ | |||||
int _total_blocks; | |||||
sem_t _blocks; /* semaphore to wake the IO thread with */ | |||||
volatile nframes_t _pending_seek; /* absolute transport position to seek to */ | |||||
int _total_blocks; /* total number of blocks that we can buffer */ | |||||
volatile nframes_t _pending_seek; /* absolute transport position to seek to */ | |||||
volatile int _terminate; | volatile int _terminate; | ||||
int channels ( void ) const { return _rb.size(); } | int channels ( void ) const { return _rb.size(); } | ||||
Audio_Track * track ( void ); | Audio_Track * track ( void ); | ||||
static void *io_thread ( void *arg ); | |||||
static void *disk_thread ( void *arg ); | |||||
protected: | protected: | ||||
@@ -77,8 +77,7 @@ protected: | |||||
} | } | ||||
} | } | ||||
void read_block ( sample_t *buf, nframes_t nframes ); | |||||
void io_thread ( void ); | |||||
virtual void disk_thread ( void ) = 0; | |||||
public: | public: | ||||
@@ -90,11 +89,14 @@ public: | |||||
virtual ~Disk_Stream ( ); | virtual ~Disk_Stream ( ); | ||||
void resize ( nframes_t nframes ); | void resize ( nframes_t nframes ); | ||||
void seek ( nframes_t frame ); | |||||
bool seek_pending ( void ); | |||||
/* void seek ( nframes_t frame ); */ | |||||
/* bool seek_pending ( void ); */ | |||||
void run ( void ); | void run ( void ); | ||||
nframes_t process ( nframes_t nframes ); | |||||
int output_buffer_percent ( void ); | |||||
virtual nframes_t process ( nframes_t nframes ) = 0; | |||||
int buffer_percent ( void ); | |||||
}; | }; |
@@ -13,12 +13,15 @@ SRCS= \ | |||||
Audio_File_SF.C \ | Audio_File_SF.C \ | ||||
Port.C \ | Port.C \ | ||||
Disk_Stream.C \ | Disk_Stream.C \ | ||||
dsp.c \ | |||||
Playback_DS.C \ | |||||
Record_DS.C \ | |||||
dsp.C \ | |||||
Engine.C \ | Engine.C \ | ||||
Transport.C \ | Transport.C \ | ||||
Loggable.C \ | Loggable.C \ | ||||
OBJS=$(SRCS:.C=.o) | |||||
OBJS:=$(SRCS:.C=.o) | |||||
# OBJS:=$(OBJS:.c=.o) | |||||
INCLUDES=-I../Engine -I../FL | INCLUDES=-I../Engine -I../FL | ||||
@@ -33,6 +36,8 @@ include ../make.inc | |||||
#LIBS:=$(LIBS) -ljack -lpthread | #LIBS:=$(LIBS) -ljack -lpthread | ||||
timeline: $(OBJS) | timeline: $(OBJS) | ||||
echo $(SRCS) >/dev/stderr | |||||
echo $(OBJS) >/dev/stderr | |||||
$(CXX) $(CXXFLAGS) $(INCLUDES) $(LIBS) -ljack -lpthread $(OBJS) -o $@ | $(CXX) $(CXXFLAGS) $(INCLUDES) $(LIBS) -ljack -lpthread $(OBJS) -o $@ | ||||
clean: | clean: | ||||
@@ -0,0 +1,227 @@ | |||||
/*******************************************************************************/ | |||||
/* Copyright (C) 2008 Jonathan Moore Liles */ | |||||
/* */ | |||||
/* 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 2 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; see the file COPYING. If not,write to the Free Software */ | |||||
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ | |||||
/*******************************************************************************/ | |||||
/* Handles streaming regions from disk to track outputs. */ | |||||
/* FIXME: we shouldn't depend on these */ | |||||
#include "Timeline.H" | |||||
#include "Engine.H" | |||||
#include "Audio_Track.H" | |||||
#include "Track_Header.H" | |||||
#include "Port.H" | |||||
#include "Playback_DS.H" | |||||
#include "dsp.h" | |||||
bool | |||||
Playback_DS::seek_pending ( void ) | |||||
{ | |||||
return _pending_seek != (nframes_t)-1; | |||||
} | |||||
/* THREAD: RT */ | |||||
/** request that the IO thread perform a seek and rebuffer. This is | |||||
called for each Disk_Stream whenever the RT thread determines that | |||||
the transport has jumped to a new position. This is called *before* | |||||
process. */ | |||||
void | |||||
Playback_DS::seek ( nframes_t frame ) | |||||
{ | |||||
printf( "requesting seek\n" ); | |||||
if ( seek_pending() ) | |||||
printf( "seek error, attempt to seek while seek is pending\n" ); | |||||
_pending_seek = frame; | |||||
/* flush buffers */ | |||||
for ( int i = channels(); i--; ) | |||||
jack_ringbuffer_read_advance( _rb[ i ], jack_ringbuffer_read_space( _rb[ i ] ) ); | |||||
/* dirty hack... reset the semaphore. Should we just call sem_init | |||||
* again instead? */ | |||||
/* sem_init( &_blocks, 0, _total_blocks ); */ | |||||
int n; | |||||
sem_getvalue( &_blocks, &n ); | |||||
n = _total_blocks - n; | |||||
while ( n-- ) | |||||
sem_post( &_blocks ); | |||||
} | |||||
/* THREAD: IO */ | |||||
/** read /nframes/ from the attached track into /buf/ */ | |||||
void | |||||
Playback_DS::read_block ( sample_t *buf, nframes_t nframes ) | |||||
{ | |||||
memset( buf, 0, nframes * sizeof( sample_t ) * channels() ); | |||||
/* stupid chicken/egg */ | |||||
if ( ! timeline ) | |||||
return; | |||||
// printf( "IO: attempting to read block @ %lu\n", _frame ); | |||||
if ( ! track() ) | |||||
{ | |||||
// _frame += _nframes; | |||||
return; | |||||
} | |||||
timeline->rdlock(); | |||||
if ( track()->play( buf, _frame, nframes, channels() ) ) | |||||
_frame += nframes; | |||||
else | |||||
/* error */; | |||||
timeline->unlock(); | |||||
} | |||||
/* THREAD: IO */ | |||||
void | |||||
Playback_DS::disk_thread ( void ) | |||||
{ | |||||
printf( "IO thread running...\n" ); | |||||
/* buffer to hold the interleaved data returned by the track reader */ | |||||
sample_t *buf = new sample_t[ _nframes * channels() ]; | |||||
const size_t block_size = _nframes * sizeof( sample_t ); | |||||
while ( wait_for_block() ) | |||||
{ | |||||
// printf( "IO: RT thread is ready for more data...\n" ); | |||||
// printf( "IO: disk buffer is %3d%% full\r", output_buffer_percent() ); | |||||
// lock(); // for seeking | |||||
if ( seek_pending() ) | |||||
{ | |||||
printf( "performing seek\n" ); | |||||
_frame = _pending_seek; | |||||
_pending_seek = -1; | |||||
/* finish flushing the buffer */ | |||||
/* for ( int i = channels(); i-- ) */ | |||||
/* jack_ringbuffer_write_advance( _rb[ i ], jack_ringbuffer_write_space( _rb[ i ] ) ); */ | |||||
} | |||||
/* FIXME: should we not read from disk in larger-than-JACK-buffer blocks? */ | |||||
read_block( buf, _nframes ); | |||||
// unlock(); // for seeking | |||||
/* deinterleave the buffer and stuff it into the per-channel ringbuffers */ | |||||
for ( int i = channels(); i--; ) | |||||
{ | |||||
while ( jack_ringbuffer_write_space( _rb[ i ] ) < block_size ) | |||||
{ | |||||
printf( "IO: disk buffer overrun!\n" ); | |||||
/* FIXME: is this *really* the right thing to do? */ | |||||
usleep( 2000 ); | |||||
} | |||||
/* deinterleave direcectly into the ringbuffer to avoid | |||||
* unnecessary copying */ | |||||
jack_ringbuffer_data_t rbd[2]; | |||||
jack_ringbuffer_get_write_vector( _rb[ i ], rbd ); | |||||
if ( rbd[ 0 ].len >= _nframes ) | |||||
/* it'll all fit in one go */ | |||||
buffer_deinterleave_one_channel( (sample_t*)rbd[ 0 ].buf, buf, i, channels(), _nframes ); | |||||
else if ( rbd[ 1 ].len ) | |||||
{ | |||||
/* there's enough space in the ringbuffer, but it's not contiguous */ | |||||
/* do the first half */ | |||||
const nframes_t f = rbd[ 1 ].len / sizeof( sample_t ); | |||||
buffer_deinterleave_one_channel( (sample_t*)rbd[ 0 ].buf, buf, i, channels(), f ); | |||||
assert( rbd[ 1 ].len >= (_nframes - f) * sizeof( sample_t ) ); | |||||
/* do the second half */ | |||||
buffer_deinterleave_one_channel( (sample_t*)rbd[ 1 ].buf, buf + f, i, channels(), _nframes - f ); | |||||
} | |||||
else | |||||
printf( "programming error: expected more space in ringbuffer\n" ); | |||||
/* buffer_deinterleave_one_channel( (sample_t*)rbd.buf, buf, i, channels(), _nframes ); */ | |||||
/* jack_ringbuffer_write( _rb[ i ], (char*)cbuf, block_size ); */ | |||||
jack_ringbuffer_write_advance( _rb[ i ], _nframes * sizeof( sample_t ) ); | |||||
} | |||||
} | |||||
printf( "IO thread terminating.\n" ); | |||||
delete[] buf; | |||||
} | |||||
/* THREAD: RT */ | |||||
/** take a single block from the ringbuffers and send it out the | |||||
* attached track's ports */ | |||||
nframes_t | |||||
Playback_DS::process ( nframes_t nframes ) | |||||
{ | |||||
const size_t block_size = nframes * sizeof( sample_t ); | |||||
// printf( "process: %lu %lu %lu\n", _frame, _frame + nframes, nframes ); | |||||
for ( int i = channels(); i--; ) | |||||
{ | |||||
void *buf = _th->output[ i ].buffer( nframes ); | |||||
if ( jack_ringbuffer_read( _rb[ i ], (char*)buf, block_size ) < block_size ) | |||||
{ | |||||
printf( "RT: buffer underrun (disk can't keep up).\n" ); | |||||
memset( buf, 0, block_size ); | |||||
/* FIXME: we need to resync somehow */ | |||||
} | |||||
/* /\* testing. *\/ */ | |||||
/* FILE *fp = fopen( "testing.au", "a" ); */ | |||||
/* fwrite( buf, block_size, 1, fp ); */ | |||||
/* fclose( fp ); */ | |||||
} | |||||
block_processed(); | |||||
/* FIXME: bogus */ | |||||
return nframes; | |||||
} |
@@ -0,0 +1,40 @@ | |||||
/*******************************************************************************/ | |||||
/* Copyright (C) 2008 Jonathan Moore Liles */ | |||||
/* */ | |||||
/* 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 2 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; see the file COPYING. If not,write to the Free Software */ | |||||
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ | |||||
/*******************************************************************************/ | |||||
#include "Disk_Stream.H" | |||||
class Playback_DS : public Disk_Stream | |||||
{ | |||||
void read_block ( sample_t *buf, nframes_t nframes ); | |||||
void disk_thread ( void ); | |||||
public: | |||||
Playback_DS ( Track_Header *th, float frame_rate, nframes_t nframes, int channels ) : | |||||
Disk_Stream( th, frame_rate, nframes, channels ) | |||||
{ | |||||
run(); | |||||
} | |||||
bool seek_pending ( void ); | |||||
void seek ( nframes_t frame ); | |||||
nframes_t process ( nframes_t nframes ); | |||||
}; |
@@ -30,11 +30,14 @@ Port::Port ( jack_port_t *port ) | |||||
_name = jack_port_name( _port ); | _name = jack_port_name( _port ); | ||||
} | } | ||||
Port::Port ( const char *name ) | |||||
Port::Port ( const char *name, direction_e dir ) | |||||
{ | { | ||||
_name = name; | _name = name; | ||||
_port = jack_port_register( engine->client(), _name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); | |||||
_port = jack_port_register( engine->client(), _name, | |||||
JACK_DEFAULT_AUDIO_TYPE, | |||||
dir == Output ? JackPortIsOutput : JackPortIsInput, | |||||
0 ); | |||||
} | } | ||||
Port::~Port ( ) | Port::~Port ( ) | ||||
@@ -49,6 +52,12 @@ Port::write ( sample_t *buf, nframes_t nframes ) | |||||
memcpy( buffer( nframes ), buf, nframes * sizeof( sample_t ) ); | memcpy( buffer( nframes ), buf, nframes * sizeof( sample_t ) ); | ||||
} | } | ||||
void | |||||
Port::read ( sample_t *buf, nframes_t nframes ) | |||||
{ | |||||
memcpy( buf, buffer( nframes ), nframes * sizeof( sample_t ) ); | |||||
} | |||||
void * | void * | ||||
Port::buffer ( nframes_t nframes ) | Port::buffer ( nframes_t nframes ) | ||||
{ | { | ||||
@@ -21,24 +21,27 @@ | |||||
#include <jack/jack.h> | #include <jack/jack.h> | ||||
typedef float sample_t; | |||||
//typedef jack_nframes_t nframes_t; | |||||
#include "types.h" | #include "types.h" | ||||
class Port | class Port | ||||
{ | { | ||||
jack_port_t *_port; | jack_port_t *_port; | ||||
const char *_name; | const char *_name; | ||||
public: | public: | ||||
enum direction_e { Output, Input }; | |||||
Port ( jack_port_t *port ); | Port ( jack_port_t *port ); | ||||
Port ( const char *name ); | |||||
Port ( const char *name, direction_e dir ); | |||||
~Port ( ); | ~Port ( ); | ||||
bool connected ( void ) const { return jack_port_connected( _port ); } | bool connected ( void ) const { return jack_port_connected( _port ); } | ||||
const char * name ( void ) const { return _name; } | const char * name ( void ) const { return _name; } | ||||
void write ( sample_t *buf, nframes_t nframes ); | void write ( sample_t *buf, nframes_t nframes ); | ||||
void read ( sample_t *buf, nframes_t nframes ); | |||||
void *buffer ( nframes_t nframes ); | void *buffer ( nframes_t nframes ); | ||||
}; | }; |
@@ -0,0 +1,155 @@ | |||||
/*******************************************************************************/ | |||||
/* Copyright (C) 2008 Jonathan Moore Liles */ | |||||
/* */ | |||||
/* 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 2 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; see the file COPYING. If not,write to the Free Software */ | |||||
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ | |||||
/*******************************************************************************/ | |||||
/* Handles streaming from track inputs to disk */ | |||||
/* FIXME: we shouldn't depend on these */ | |||||
#include "Timeline.H" | |||||
#include "Engine.H" | |||||
#include "Audio_Track.H" | |||||
#include "Track_Header.H" | |||||
#include "Port.H" | |||||
#include "Record_DS.H" | |||||
#include "dsp.h" | |||||
/* THREAD: IO */ | |||||
/** write /nframes/ from buf to the capture file of the attached track */ | |||||
void | |||||
Record_DS::write_block ( sample_t *buf, nframes_t nframes ) | |||||
{ | |||||
/* stupid chicken/egg */ | |||||
if ( ! ( timeline && track() ) ) | |||||
return; | |||||
// timeline->wrlock(); | |||||
_af->write( buf, nframes ); | |||||
// track()->record( buf, _frame, nframes, channels() ); | |||||
// timeline->unlock(); | |||||
} | |||||
/* THREAD: IO */ | |||||
void | |||||
Record_DS::disk_thread ( void ) | |||||
{ | |||||
printf( "IO thread running...\n" ); | |||||
/* buffer to hold the interleaved data returned by the track reader */ | |||||
sample_t *buf = new sample_t[ _nframes * channels() ]; | |||||
sample_t *cbuf = new sample_t[ _nframes ]; | |||||
const size_t block_size = _nframes * sizeof( sample_t ); | |||||
while ( wait_for_block() ) | |||||
{ | |||||
/* pull data from the per-channel ringbuffers and interlace it */ | |||||
for ( int i = channels(); i--; ) | |||||
{ | |||||
while ( jack_ringbuffer_read_space( _rb[ i ] ) < block_size ) | |||||
{ | |||||
printf( "IO: disk buffer underrun!\n" ); | |||||
/* FIXME: is this *really* the right thing to do? */ | |||||
usleep( 2000 ); | |||||
} | |||||
/* FIXME: avoid this copy */ | |||||
jack_ringbuffer_read( _rb[ i ], (char*)cbuf, block_size ); | |||||
buffer_interleave_one_channel( buf, cbuf, i, channels(), _nframes ); | |||||
/* /\* deinterleave direcectly into the ringbuffer to avoid */ | |||||
/* * unnecessary copying *\/ */ | |||||
/* jack_ringbuffer_data_t rbd[2]; */ | |||||
/* jack_ringbuffer_get_write_vector( _rb[ i ], rbd ); */ | |||||
/* if ( rbd[ 0 ].len >= _nframes ) */ | |||||
/* /\* it'll all fit in one go *\/ */ | |||||
/* buffer_deinterleave_one_channel( (sample_t*)rbd[ 0 ].buf, buf, i, channels(), _nframes ); */ | |||||
/* else if ( rbd[ 1 ].len ) */ | |||||
/* { */ | |||||
/* /\* there's enough space in the ringbuffer, but it's not contiguous *\/ */ | |||||
/* /\* do the first half *\/ */ | |||||
/* const nframes_t f = rbd[ 1 ].len / sizeof( sample_t ); */ | |||||
/* buffer_deinterleave_one_channel( (sample_t*)rbd[ 0 ].buf, buf, i, channels(), f ); */ | |||||
/* assert( rbd[ 1 ].len >= (_nframes - f) * sizeof( sample_t ) ); */ | |||||
/* /\* do the second half *\/ */ | |||||
/* buffer_deinterleave_one_channel( (sample_t*)rbd[ 1 ].buf, buf + f, i, channels(), _nframes - f ); */ | |||||
/* } */ | |||||
/* else */ | |||||
/* printf( "programming error: expected more space in ringbuffer\n" ); */ | |||||
/* /\* buffer_deinterleave_one_channel( (sample_t*)rbd.buf, buf, i, channels(), _nframes ); *\/ */ | |||||
/* /\* jack_ringbuffer_write( _rb[ i ], (char*)cbuf, block_size ); *\/ */ | |||||
/* jack_ringbuffer_write_advance( _rb[ i ], _nframes * sizeof( sample_t ) ); */ | |||||
} | |||||
write_block( buf, _nframes ); | |||||
} | |||||
printf( "IO thread terminating.\n" ); | |||||
delete[] buf; | |||||
} | |||||
/* THREAD: RT */ | |||||
/** take a single block from the ringbuffers and send it out the | |||||
* attached track's ports */ | |||||
nframes_t | |||||
Record_DS::process ( nframes_t nframes ) | |||||
{ | |||||
const size_t block_size = nframes * sizeof( sample_t ); | |||||
// printf( "process: %lu %lu %lu\n", _frame, _frame + nframes, nframes ); | |||||
for ( int i = channels(); i--; ) | |||||
{ | |||||
void *buf = _th->input[ i ].buffer( nframes ); | |||||
if ( jack_ringbuffer_write( _rb[ i ], (char*)buf, block_size ) < block_size ) | |||||
{ | |||||
printf( "RT: buffer overrun (disk can't keep up).\n" ); | |||||
memset( buf, 0, block_size ); | |||||
/* FIXME: we need to resync somehow */ | |||||
} | |||||
} | |||||
block_processed(); | |||||
/* FIXME: bogus */ | |||||
return nframes; | |||||
} |
@@ -0,0 +1,55 @@ | |||||
/*******************************************************************************/ | |||||
/* Copyright (C) 2008 Jonathan Moore Liles */ | |||||
/* */ | |||||
/* 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 2 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; see the file COPYING. If not,write to the Free Software */ | |||||
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ | |||||
/*******************************************************************************/ | |||||
#pragma once | |||||
#include "Disk_Stream.H" | |||||
#include "Audio_File_SF.H" | |||||
class Audio_File; | |||||
class Record_DS : public Disk_Stream | |||||
{ | |||||
Audio_File_SF *_af; /* capture file */ | |||||
void write_block ( sample_t *buf, nframes_t nframes ); | |||||
void disk_thread ( void ); | |||||
public: | |||||
Record_DS ( Track_Header *th, float frame_rate, nframes_t nframes, int channels ) : | |||||
Disk_Stream( th, frame_rate, nframes, channels ) | |||||
{ | |||||
/* FIXME: we need our semaphore set to 0, no? */ | |||||
_af = Audio_File_SF::create( "testing.wav", 48000, 1, "Wav/24" ); | |||||
sem_destroy( &_blocks ); | |||||
sem_init( &_blocks, 0, 0 ); | |||||
run(); | |||||
} | |||||
/* bool seek_pending ( void ); */ | |||||
/* void seek ( nframes_t frame ); */ | |||||
nframes_t process ( nframes_t nframes ); | |||||
}; |
@@ -709,7 +709,8 @@ Region::Fade::apply ( sample_t *buf, Region::Fade::fade_dir_e dir, long start, n | |||||
const float inc = increment(); | const float inc = increment(); | ||||
float fi = ( i - start ) / (float)length; | float fi = ( i - start ) / (float)length; | ||||
buf += i; | |||||
// buf += i; | |||||
buf = &buf[ i ]; | |||||
nframes_t n = e - i; | nframes_t n = e - i; | ||||
@@ -32,7 +32,7 @@ | |||||
const float UPDATE_FREQ = 0.02f; | const float UPDATE_FREQ = 0.02f; | ||||
#include "Disk_Stream.H" | |||||
#include "Playback_DS.H" | |||||
#include "Transport.H" | #include "Transport.H" | ||||
@@ -160,7 +160,7 @@ Timeline::Timeline ( int X, int Y, int W, int H, const char* L ) : Fl_Overlay_Wi | |||||
o->type( Fl_Pack::VERTICAL ); | o->type( Fl_Pack::VERTICAL ); | ||||
o->spacing( 0 ); | o->spacing( 0 ); | ||||
for ( int i = 2; i--; ) | |||||
for ( int i = 1; i--; ) | |||||
{ | { | ||||
// Track_Header *t = new Track_Header( 0, 0, W, 75 ); | // Track_Header *t = new Track_Header( 0, 0, W, 75 ); | ||||
Track_Header *t = new Track_Header( 0, 0, W, 30 ); | Track_Header *t = new Track_Header( 0, 0, W, 30 ); | ||||
@@ -699,7 +699,7 @@ Timeline::seek_pending ( void ) | |||||
{ | { | ||||
Track_Header *t = (Track_Header*)tracks->child( i ); | Track_Header *t = (Track_Header*)tracks->child( i ); | ||||
if ( t->diskstream ) | |||||
r += t->diskstream->output_buffer_percent() < 50; | |||||
if ( t->playback_ds ) | |||||
r += t->playback_ds->buffer_percent() < 50; | |||||
} | } | ||||
} | } |
@@ -19,9 +19,13 @@ | |||||
#include "Track_Header.H" | #include "Track_Header.H" | ||||
#include "Disk_Stream.H" | |||||
#include "Playback_DS.H" | |||||
#include "Record_DS.H" | |||||
#include "Engine.H" | #include "Engine.H" | ||||
#include "Port.H" | |||||
void | void | ||||
Track_Header::cb_input_field ( Fl_Widget *w, void *v ) | Track_Header::cb_input_field ( Fl_Widget *w, void *v ) | ||||
{ | { | ||||
@@ -88,8 +92,6 @@ Track_Header::cb_button ( Fl_Widget *w ) | |||||
} | } | ||||
} | } | ||||
#include "Port.H" | |||||
Track_Header::Track_Header ( int X, int Y, int W, int H, const char *L ) : | Track_Header::Track_Header ( int X, int Y, int W, int H, const char *L ) : | ||||
Fl_Group ( X, Y, W, H, L ) | Fl_Group ( X, Y, W, H, L ) | ||||
{ | { | ||||
@@ -102,13 +104,20 @@ Track_Header::Track_Header ( int X, int Y, int W, int H, const char *L ) : | |||||
{ | { | ||||
char pname[40]; | char pname[40]; | ||||
static int n = 0; | |||||
snprintf( pname, sizeof( pname ), "out-%d", n++ ); | |||||
static int no = 0, ni = 0; | |||||
snprintf( pname, sizeof( pname ), "out-%d", no++ ); | |||||
output.push_back( Port( strdup( pname ), Port::Output ) ); | |||||
snprintf( pname, sizeof( pname ), "in-%d", ni++ ); | |||||
input.push_back( Port( strdup( pname ), Port::Input ) ); | |||||
output.push_back( Port( strdup( pname ) ) ); | |||||
} | } | ||||
diskstream = new Disk_Stream( this, engine->frame_rate(), engine->nframes(), 1 ); | |||||
playback_ds = new Playback_DS( this, engine->frame_rate(), engine->nframes(), 1 ); | |||||
record_ds = new Record_DS( this, engine->frame_rate(), engine->nframes(), 1 ); | |||||
Fl_Group::size( w(), height() ); | Fl_Group::size( w(), height() ); | ||||
@@ -300,8 +309,11 @@ Track_Header::add_control( Track *t ) | |||||
nframes_t | nframes_t | ||||
Track_Header::process ( nframes_t nframes ) | Track_Header::process ( nframes_t nframes ) | ||||
{ | { | ||||
if ( diskstream ) | |||||
return diskstream->process( nframes ); | |||||
if ( playback_ds ) | |||||
{ | |||||
record_ds->process( nframes ); | |||||
return playback_ds->process( nframes ); | |||||
} | |||||
else | else | ||||
return 0; | return 0; | ||||
} | } | ||||
@@ -310,6 +322,6 @@ Track_Header::process ( nframes_t nframes ) | |||||
void | void | ||||
Track_Header::seek ( nframes_t frame ) | Track_Header::seek ( nframes_t frame ) | ||||
{ | { | ||||
if ( diskstream ) | |||||
return diskstream->seek( frame ); | |||||
if ( playback_ds ) | |||||
return playback_ds->seek( frame ); | |||||
} | } |
@@ -30,11 +30,16 @@ | |||||
#include "Loggable.H" | #include "Loggable.H" | ||||
#include "Port.H" | |||||
// #include "Port.H" | |||||
#include <vector> | #include <vector> | ||||
using std::vector; | using std::vector; | ||||
class Disk_Stream; | |||||
class Playback_DS; | |||||
class Record_DS; | |||||
class Port; | |||||
class Track_Header : public Fl_Group, public Loggable | class Track_Header : public Fl_Group, public Loggable | ||||
{ | { | ||||
@@ -73,8 +78,11 @@ public: | |||||
Fl_Pack *control; | Fl_Pack *control; | ||||
Fl_Pack *takes; | Fl_Pack *takes; | ||||
vector <Port> input; | |||||
vector <Port> output; /* output ports... */ | vector <Port> output; /* output ports... */ | ||||
Disk_Stream *diskstream; | |||||
Playback_DS *playback_ds; | |||||
Record_DS *record_ds; | |||||
const char *class_name ( void ) { return "Track_Header"; } | const char *class_name ( void ) { return "Track_Header"; } | ||||