Browse Source

Work on making diskstreams follow the transport.

tags/non-daw-v1.1.0
Jonathan Moore Liles 17 years ago
parent
commit
904daf8fe4
10 changed files with 269 additions and 51 deletions
  1. +3
    -3
      Timeline/Audio_File.C
  2. +119
    -26
      Timeline/Disk_Stream.C
  3. +25
    -18
      Timeline/Disk_Stream.H
  4. +61
    -0
      Timeline/Engine.C
  5. +4
    -2
      Timeline/Engine.H
  6. +6
    -0
      Timeline/Peaks.C
  7. +31
    -1
      Timeline/Timeline.C
  8. +3
    -0
      Timeline/Timeline.H
  9. +15
    -1
      Timeline/Track_Header.C
  10. +2
    -0
      Timeline/Track_Header.H

+ 3
- 3
Timeline/Audio_File.C View File

@@ -41,10 +41,10 @@ Audio_File::from_file ( const char * filename )


done: done:


a->_peaks = new Peaks;
/* a->_peaks = new Peaks; */


a->_peaks->clip( a );
a->_peaks->open();
/* a->_peaks->clip( a ); */
/* a->_peaks->open(); */


_open_files[ string( filename ) ] = a; _open_files[ string( filename ) ] = a;




+ 119
- 26
Timeline/Disk_Stream.C View File

@@ -21,33 +21,42 @@
#include "Track_Header.H" #include "Track_Header.H"
#include "Audio_Track.H" #include "Audio_Track.H"
#include "Port.H" #include "Port.H"
#include "Engine.H" // for locking.


// float Disk_Stream::seconds_to_buffer = 5.0f;
float Disk_Stream::seconds_to_buffer = 5.0f;
// size_t Disk_Stream::disk_block_frames = 2048;
/**********/
/* Engine */
/**********/


/* A Disk_Stream uses a separate I/O thread to stream a track's /* A Disk_Stream uses a separate I/O thread to stream a track's
regions from disk into a ringbuffer, to be processed by the RT regions from disk into a ringbuffer, to be processed by the RT
thread (or vice-versa). */
thread (or vice-versa). The I/O thread syncronizes access with the
user thread via the Timeline mutex. The size of the buffer (in
seconds) must be set before any Disk_Stream objects are created;
that is, at startup time. The default is 5 seconds, which may or
may not be excessive depending on various external factors. */

/* FIXME: handle termination of IO thread in destructor */ /* FIXME: handle termination of IO thread in destructor */
/* FIXME: could all of this not simply be included in the Track_Header
class? */
/* FIXME: deal with (jack) buffer size changes */ /* FIXME: deal with (jack) buffer size changes */
/* FIXME: can this be made to actually handle capture? */
/* FIXME: needs error handling everywhere! */ /* FIXME: needs error handling everywhere! */
/* TODO: handle capture too */

float Disk_Stream::seconds_to_buffer = 1.0f;
// size_t Disk_Stream::disk_block_frames = 2048;


Disk_Stream::Disk_Stream ( Track_Header *th, float frame_rate, nframes_t nframes, int channels ) : _th( th ) Disk_Stream::Disk_Stream ( Track_Header *th, float frame_rate, nframes_t nframes, int channels ) : _th( th )
{ {
_frame = 0; _frame = 0;
_thread = 0; _thread = 0;
_terminate = false;
_pending_seek = -1;


printf( "nframes %lu\n", nframes ); printf( "nframes %lu\n", nframes );


const int blocks = frame_rate * seconds_to_buffer / nframes;
_total_blocks = frame_rate * seconds_to_buffer / nframes;


_nframes = nframes; _nframes = nframes;


size_t bufsize = blocks * nframes * sizeof( sample_t );
size_t bufsize = _total_blocks * nframes * sizeof( sample_t );


/* const int blocks = 64; */ /* const int blocks = 64; */
/* const size_t bufsize = (blocks * (nframes * sizeof( sample_t ))) + sizeof( sample_t ); */ /* const size_t bufsize = (blocks * (nframes * sizeof( sample_t ))) + sizeof( sample_t ); */
@@ -55,19 +64,29 @@ Disk_Stream::Disk_Stream ( Track_Header *th, float frame_rate, nframes_t nframes
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, blocks );
sem_init( &_blocks, 0, _total_blocks );


run(); run();
} }


Disk_Stream::~Disk_Stream ( ) Disk_Stream::~Disk_Stream ( )
{ {

/* stop the IO thread */
_terminate = true;
pthread_join( _thread, NULL );

/* it isn't safe to do all this with the RT thread running */
engine->lock();

_th = NULL; _th = NULL;


sem_destroy( &_blocks ); sem_destroy( &_blocks );


for ( int i = channels(); i--; ) for ( int i = channels(); i--; )
jack_ringbuffer_free( _rb[ i ] ); jack_ringbuffer_free( _rb[ i ] );

engine->unlock();
} }


Audio_Track * Audio_Track *
@@ -84,6 +103,56 @@ Disk_Stream::run ( void )
/* error */; /* error */;
} }


/* to be called when the JACK buffer size changes. */
void
Disk_Stream::resize ( nframes_t nframes )
{
if ( nframes != _nframes )
/* 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 ) */
/* { */ /* { */
@@ -100,9 +169,9 @@ Disk_Stream::io_thread ( void *arg )
} }


/* THREAD: IO */ /* THREAD: IO */
/** read a block of data from the track into /buf/ */
/** read /nframes/ from the attached track into /buf/ */
void void
Disk_Stream::read_block ( sample_t *buf )
Disk_Stream::read_block ( sample_t *buf, nframes_t nframes )
{ {


/* stupid chicken/egg */ /* stupid chicken/egg */
@@ -119,14 +188,24 @@ Disk_Stream::read_block ( sample_t *buf )


timeline->rdlock(); timeline->rdlock();


if ( track()->play( buf, _frame, _nframes, channels() ) )
_frame += _nframes;
if ( track()->play( buf, _frame, nframes, channels() ) )
_frame += nframes;
else else
/* error */; /* error */;


timeline->unlock(); timeline->unlock();
} }


int
Disk_Stream::output_buffer_percent ( void )
{
int n;

sem_getvalue( &_blocks, &n );

return 100 - (n * 100 / _total_blocks);
}

/* THREAD: IO */ /* THREAD: IO */
void void
Disk_Stream::io_thread ( void ) Disk_Stream::io_thread ( void )
@@ -145,7 +224,26 @@ Disk_Stream::io_thread ( void )
{ {
// printf( "IO: RT thread is ready for more data...\n" ); // printf( "IO: RT thread is ready for more data...\n" );


read_block( buf );
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 */ /* deinterleave the buffer and stuff it into the per-channel ringbuffers */


@@ -158,6 +256,7 @@ Disk_Stream::io_thread ( void )
while ( jack_ringbuffer_write_space( _rb[ i ] ) < block_size ) while ( jack_ringbuffer_write_space( _rb[ i ] ) < block_size )
{ {
printf( "IO: disk buffer overrun!\n" ); printf( "IO: disk buffer overrun!\n" );
/* FIXME: is this *really* the right thing to do? */
usleep( 2000 ); usleep( 2000 );
} }


@@ -165,13 +264,15 @@ Disk_Stream::io_thread ( void )
} }
} }


printf( "IO thread terminating.\n" );

delete[] buf; delete[] buf;
delete[] cbuf; delete[] cbuf;
} }


/* THREAD: RT */ /* THREAD: RT */
/** take a block from the ringbuffers and send it out the track's
* ports */
/** take a single block from the ringbuffers and send it out the
* attached track's ports */
nframes_t nframes_t
Disk_Stream::process ( nframes_t nframes ) Disk_Stream::process ( nframes_t nframes )
{ {
@@ -184,19 +285,11 @@ Disk_Stream::process ( nframes_t nframes )


void *buf = _th->output[ i ].buffer( nframes ); void *buf = _th->output[ i ].buffer( nframes );


/* FIXME: handle underrun */

/* if ( jack_ringbuffer_read_space( _rb[ i ] ) < block_size ) */
/* { */
/* printf( "disktream (rt): buffer underrun!\n" ); */
/* memset( buf, 0, block_size ); */
/* } */
/* else */

if ( jack_ringbuffer_read( _rb[ i ], (char*)buf, block_size ) < block_size ) if ( jack_ringbuffer_read( _rb[ i ], (char*)buf, block_size ) < block_size )
{ {
printf( "RT: buffer underrun (disk can't keep up).\n" ); printf( "RT: buffer underrun (disk can't keep up).\n" );
memset( buf, 0, block_size ); memset( buf, 0, block_size );
/* FIXME: we need to resync somehow */
} }


/* /\* testing. *\/ */ /* /\* testing. *\/ */


+ 25
- 18
Timeline/Disk_Stream.H View File

@@ -27,13 +27,15 @@


#include <pthread.h> #include <pthread.h>


#include "Mutex.H"

#include <vector> #include <vector>
using std::vector; using std::vector;


class Track_Header; class Track_Header;
class Audio_Track; class Audio_Track;


class Disk_Stream
class Disk_Stream : public Mutex
{ {


pthread_t _thread; pthread_t _thread;
@@ -49,7 +51,11 @@ class Disk_Stream


sem_t _blocks; /* semaphore to wake the IO thread with */ sem_t _blocks; /* semaphore to wake the IO thread with */


// volatile nframes_t _seek_request;
int _total_blocks;

volatile nframes_t _pending_seek; /* absolute transport position to seek to */

volatile int _terminate;


int channels ( void ) const { return _rb.size(); } int channels ( void ) const { return _rb.size(); }


@@ -60,7 +66,19 @@ class Disk_Stream
protected: protected:


void block_processed ( void ) { sem_post( &_blocks ); } void block_processed ( void ) { sem_post( &_blocks ); }
bool wait_for_block ( void ) { while ( sem_wait( &_blocks ) == EINTR ); return true; }
bool wait_for_block ( void )
{
if ( _terminate )
return false;
else
{
while ( sem_wait( &_blocks ) == EINTR );
return true;
}
}

void read_block ( sample_t *buf, nframes_t nframes );
void io_thread ( void );


public: public:


@@ -71,23 +89,12 @@ public:


virtual ~Disk_Stream ( ); virtual ~Disk_Stream ( );


void
resize ( nframes_t nframes )
{
if ( nframes != _nframes )
/* FIXME: to something here! */;
}

void
seek ( nframes_t frame )
{
_frame = frame;
/* FIXME: need to signal the IO thread somehow? */
}
void resize ( nframes_t nframes );
void seek ( nframes_t frame );
bool seek_pending ( void );


void run ( void ); void run ( void );
void read_block ( sample_t *buf );
void io_thread ( void );
nframes_t process ( nframes_t nframes ); nframes_t process ( nframes_t nframes );


int output_buffer_percent ( void );
}; };

+ 61
- 0
Timeline/Engine.C View File

@@ -40,6 +40,63 @@ Engine::process ( nframes_t nframes, void *arg )
return ((Engine*)arg)->process( nframes ); return ((Engine*)arg)->process( nframes );
} }


/* static wrapper */
int
Engine::sync ( jack_transport_state_t state, jack_position_t *pos, void *arg )
{
return ((Engine*)arg)->sync( state, pos );
}

void
Engine::request_locate ( nframes_t frame )
{
if ( timeline )
timeline->seek( frame );
}

/* THREAD: RT */
/** This is the jack slow-sync callback. */
int
Engine::sync ( jack_transport_state_t state, jack_position_t *pos )
{
static bool seeking = false;

switch ( state )
{
case JackTransportStopped: /* new position requested */
/* JACK docs lie. This is only called when the transport
is *really* stopped, not when starting a slow-sync
cycle */
request_locate( pos->frame );
return 1;
case JackTransportStarting: /* this means JACK is polling slow-sync clients */
{
if ( ! seeking )
{
request_locate( pos->frame );
seeking = true;
}

int r = timeline->seek_pending();

if ( ! r )
seeking = false;

return ! seeking;
}
case JackTransportRolling: /* JACK's timeout has expired */
/* FIXME: what's the right thing to do here? */
// request_locate( pos->frame );
return 1;
// return transport.frame == pos->frame;
break;
default:
printf( "unknown transport state.\n" );
}

return 0;
}

/* THREAD: RT */ /* THREAD: RT */
int int
Engine::process ( nframes_t nframes ) Engine::process ( nframes_t nframes )
@@ -83,6 +140,10 @@ Engine::init ( void )


jack_set_process_callback( _client, &Engine::process, this ); jack_set_process_callback( _client, &Engine::process, this );


/* FIXME: should we wait to register this until after the session
has been loaded (and we have disk threads running)? */
jack_set_sync_callback( _client, &Engine::sync, this );

jack_activate( _client ); jack_activate( _client );


/* we don't need to create any ports until tracks are created */ /* we don't need to create any ports until tracks are created */


+ 4
- 2
Timeline/Engine.H View File

@@ -44,6 +44,8 @@ class Engine : public Mutex


static int process ( nframes_t nframes, void *arg ); static int process ( nframes_t nframes, void *arg );
int process ( nframes_t nframes ); int process ( nframes_t nframes );
static int sync ( jack_transport_state_t state, jack_position_t *pos, void *arg );
int sync ( jack_transport_state_t state, jack_position_t *pos );


private: private:


@@ -52,12 +54,12 @@ private:


public: public:




Engine ( ); Engine ( );


int init ( void ); int init ( void );


void request_locate ( nframes_t frame );

nframes_t nframes ( void ) const { return jack_get_buffer_size( _client ); } nframes_t nframes ( void ) const { return jack_get_buffer_size( _client ); }
float frame_rate ( void ) const { return jack_get_sample_rate( _client ); } float frame_rate ( void ) const { return jack_get_sample_rate( _client ); }




+ 6
- 0
Timeline/Peaks.C View File

@@ -38,6 +38,8 @@


#include <math.h> #include <math.h>


#include <FL/Fl.H> // for Fl::check();

Peaks::peakbuffer Peaks::_peakbuf; Peaks::peakbuffer Peaks::_peakbuf;




@@ -305,6 +307,8 @@ Peaks::current ( void ) const
} }




/* FIXME: we need to work out a way to run this in another thread and
possibly stream back the data to the GUI */
/** build peaks file for /filename/ if necessary */ /** build peaks file for /filename/ if necessary */
bool bool
Peaks::make_peaks ( int chunksize ) Peaks::make_peaks ( int chunksize )
@@ -334,6 +338,8 @@ Peaks::make_peaks ( int chunksize )
do { do {
len = read_source_peaks( peaks, 1, chunksize ); len = read_source_peaks( peaks, 1, chunksize );
fwrite( peaks, sizeof( peaks ), 1, fp ); fwrite( peaks, sizeof( peaks ), 1, fp );
/* FIXME: GUI code shouldn't be here! */
Fl::check();
} }
while ( len ); while ( len );




+ 31
- 1
Timeline/Timeline.C View File

@@ -29,6 +29,9 @@


#include "Track_Header.H" #include "Track_Header.H"



#include "Disk_Stream.H"

void void
Timeline::cb_scroll ( Fl_Widget *w, void *v ) Timeline::cb_scroll ( Fl_Widget *w, void *v )
{ {
@@ -153,7 +156,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 = 1; i--; )
for ( int i = 2; 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 );
@@ -616,3 +619,30 @@ Timeline::process ( nframes_t nframes )
/* FIXME: BOGUS */ /* FIXME: BOGUS */
return nframes; return nframes;
} }

/* THREAD: RT */
void
Timeline::seek ( nframes_t frame )
{
for ( int i = tracks->children(); i-- ; )
{
Track_Header *t = (Track_Header*)tracks->child( i );

t->seek( frame );
}
}

/* THREAD: RT */
int
Timeline::seek_pending ( void )
{
int r = 0;

for ( int i = tracks->children(); i-- ; )
{
Track_Header *t = (Track_Header*)tracks->child( i );

if ( t->diskstream )
r += t->diskstream->output_buffer_percent() < 50;
}
}

+ 3
- 0
Timeline/Timeline.H View File

@@ -148,5 +148,8 @@ private:


friend class Engine; // FIXME: only Engine::process() needs to be friended.x friend class Engine; // FIXME: only Engine::process() needs to be friended.x


/* Engine */
nframes_t process ( nframes_t nframes ); nframes_t process ( nframes_t nframes );
void seek ( nframes_t frame );
int seek_pending ( void );
}; };

+ 15
- 1
Timeline/Track_Header.C View File

@@ -100,7 +100,13 @@ Track_Header::Track_Header ( int X, int Y, int W, int H, const char *L ) :
_show_all_takes = false; _show_all_takes = false;
_size = 1; _size = 1;


output.push_back( Port( "foo" ) );
{
char pname[40];
static int n = 0;
snprintf( pname, sizeof( pname ), "out-%d", n++ );

output.push_back( Port( strdup( pname ) ) );
}


diskstream = new Disk_Stream( this, engine->frame_rate(), engine->nframes(), 1 ); diskstream = new Disk_Stream( this, engine->frame_rate(), engine->nframes(), 1 );


@@ -299,3 +305,11 @@ Track_Header::process ( nframes_t nframes )
else else
return 0; return 0;
} }

/* THREAD: RT */
void
Track_Header::seek ( nframes_t frame )
{
if ( diskstream )
return diskstream->seek( frame );
}

+ 2
- 0
Timeline/Track_Header.H View File

@@ -264,6 +264,8 @@ public:
} }




/* Engine */
nframes_t process ( nframes_t nframes ); nframes_t process ( nframes_t nframes );
void seek ( nframes_t frame );
}; };
#endif #endif

Loading…
Cancel
Save