Actually get playback through JACK, kind of.

This commit is contained in:
Jonathan Moore Liles 2008-04-08 19:05:15 -05:00
parent 0dff4be584
commit 7f43a99030
15 changed files with 323 additions and 17 deletions

View File

@ -25,6 +25,8 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
Audio_File_SF *
Audio_File_SF::from_file ( const char *filename )
{
@ -100,6 +102,11 @@ Audio_File_SF::read ( sample_t *buf, int channel, nframes_t len )
return sf_readf_float( _in, buf, len );
else
{
if ( len > 256 * 100 )
printf( "warning: attempt to read an insane number of frames (%lu) from soundfile\n", len );
printf( "len = %lu, channels = %d\n", len, _channels );
sample_t *tmp = new sample_t[ len * _channels ];
nframes_t rlen = sf_readf_float( _in, tmp, len );
@ -118,6 +125,8 @@ Audio_File_SF::read ( sample_t *buf, int channel, nframes_t len )
nframes_t
Audio_File_SF::read ( sample_t *buf, int channel, nframes_t start, nframes_t end )
{
assert( end > start );
open();
seek( start );

View File

@ -99,7 +99,8 @@ Audio_Track::handle ( int m )
return 0;
}
Region *r = new Region( c, this, timeline->xoffset + timeline->x_to_ts( Fl::event_x() - x() ) );
// Region *r =
new Region( c, this, timeline->xoffset + timeline->x_to_ts( Fl::event_x() - x() ) );
redraw();
return 1;
@ -123,7 +124,7 @@ Audio_Track::play ( sample_t *buf, nframes_t frame, nframes_t nframes, int chann
sample_t *cbuf = new sample_t[ nframes ];
/* quick and dirty--let the regions figure out coverage for themselves */
for ( list <Track_Widget *>::const_iterator i = _widgets.begin(); i != _widgets.end(); i++ )
for ( list <Track_Widget *>::const_iterator i = _widgets.begin(); i != _widgets.end(); ++i )
{
const Region *r = (Region*)(*i);
@ -141,6 +142,15 @@ Audio_Track::play ( sample_t *buf, nframes_t frame, nframes_t nframes, int chann
}
}
delete[] cbuf;
/* FIXME: bogus */
return nframes;
}
/* /\* THREAD: RT *\/ */
/* nframes_t */
/* Audio_Track::process ( nframes_t nframes ) */
/* { */
/* return disktream->process( nframes ); */
/* } */

View File

@ -46,7 +46,7 @@ Disk_Stream::Disk_Stream ( Track_Header *th, float frame_rate, nframes_t nframes
size_t bufsize = blocks * nframes * sizeof( sample_t );
for ( int i = channels; i--; )
_rb[ i ] = jack_ringbuffer_create( bufsize );
_rb.push_back( jack_ringbuffer_create( bufsize ) );
sem_init( &_blocks, 0, blocks );
@ -97,10 +97,27 @@ Disk_Stream::io_thread ( void *arg )
void
Disk_Stream::read_block ( sample_t *buf )
{
/* 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 */
@ -119,6 +136,8 @@ Disk_Stream::io_thread ( void )
while ( wait_for_block() )
{
// printf( "IO: RT thread is ready for more data...\n" );
read_block( buf );
/* deinterleave the buffer and stuff it into the per-channel ringbuffers */
@ -140,8 +159,8 @@ Disk_Stream::io_thread ( void )
/* THREAD: RT */
/** take a block from the ringbuffers and send it out the track's
* ports */
void
Disk_Stream::process ( void )
nframes_t
Disk_Stream::process ( nframes_t nframes )
{
const size_t block_size = _nframes * sizeof( sample_t );
@ -154,4 +173,7 @@ Disk_Stream::process ( void )
}
block_processed();
/* FIXME: bogus */
return nframes;
}

View File

@ -86,6 +86,6 @@ public:
void run ( void );
void read_block ( sample_t *buf );
void io_thread ( void );
void process ( void );
nframes_t process ( nframes_t nframes );
};

91
Timeline/Engine.C Normal file
View File

@ -0,0 +1,91 @@
/*******************************************************************************/
/* 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 "Engine.H"
#include "Timeline.H" // for process()
#define APP_NAME "Non-DAW" // FIXME: wrong place for this!
/* This is the home of the JACK process callback (does this *really*
need to be a class?) */
Engine::Engine ( )
{
_client = NULL;
_buffers_dropped = 0;
}
/* static wrapper */
int
Engine::process ( nframes_t nframes, void *arg )
{
return ((Engine*)arg)->process( nframes );
}
/* THREAD: RT */
int
Engine::process ( nframes_t nframes )
{
jack_position_t pos;
jack_transport_state_t ts;
ts = jack_transport_query( _client, &pos );
if ( ts != JackTransportRolling )
return 0;
if ( ! trylock() )
{
/* the data structures we need to access here (tracks and
* their ports, but not track contents) may be in an
* inconsistent state at the moment. Just punt and drop this
* buffer. */
++_buffers_dropped;
return 0;
}
/* handle chicken/egg problem */
if ( timeline )
/* this will initiate the process() call graph for the various
* number and types of tracks, which will in turn send data out
* the appropriate ports. */
timeline->process( nframes );
unlock();
return 0;
}
int
Engine::init ( void )
{
if (( _client = jack_client_open ( APP_NAME, (jack_options_t)0, NULL )) == 0 )
return 0;
jack_set_process_callback( _client, &Engine::process, this );
jack_activate( _client );
/* we don't need to create any ports until tracks are created */
return 1;
}

67
Timeline/Engine.H Normal file
View File

@ -0,0 +1,67 @@
/*******************************************************************************/
/* 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 "Mutex.H"
#include <jack/jack.h>
typedef jack_nframes_t nframes_t;
class Port;
class Engine : public Mutex
{
jack_client_t *_client;
/* I know locking out the process callback is cheating, even
though we use trylock... The thing is, every other DAW does
this too and you can hear it in the glitches Ardour and friends
produce when reconfiguring I/O... Working out a message queue
system would obviously be better, but a DAW isn't a performance
instrument anyway, so I think these drop-outs are a reasonable
compromise. Obviously, this lock should never be held during
blocking operations. */
int _buffers_dropped; /* buffers dropped because of locking */
static int process ( nframes_t nframes, void *arg );
int process ( nframes_t nframes );
private:
friend class Port;
jack_client_t * client ( void ) { return _client; }
public:
Engine ( );
int init ( void );
nframes_t nframes ( void ) const { return jack_get_buffer_size( _client ); }
float frame_rate ( void ) const { return jack_get_sample_rate( _client ); }
};
extern Engine * engine;

View File

@ -31,6 +31,12 @@ public:
Mutex ( )
{
// _lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
pthread_mutex_init( &_lock, NULL );
}
virtual ~Mutex ( )
{
pthread_mutex_destroy( &_lock );
}
void

View File

@ -21,6 +21,8 @@
#include <string.h>
#include "Engine.H"
/* nframes is the number of frames to buffer */
Port::Port ( jack_port_t *port )
{
@ -28,9 +30,17 @@ Port::Port ( jack_port_t *port )
_name = jack_port_name( _port );
}
Port::Port ( const char *name )
{
_name = name;
_port = jack_port_register( engine->client(), _name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 );
}
Port::~Port ( )
{
/* close port? */
// jack_port_unregister( engine->client(), _port );
}
void

View File

@ -33,6 +33,7 @@ class Port
public:
Port ( jack_port_t *port );
Port ( const char *name );
~Port ( );
bool connected ( void ) const { return jack_port_connected( _port ); }

72
Timeline/RWLock.H Normal file
View File

@ -0,0 +1,72 @@
/*******************************************************************************/
/* 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 <pthread.h>
class RWLock
{
pthread_rwlock_t _lock;
public:
RWLock ( )
{
// _lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
pthread_rwlock_init( &_lock, NULL );
}
virtual ~RWLock ( )
{
pthread_rwlock_destroy( &_lock );
}
void
rdlock ( void )
{
pthread_rwlock_rdlock( &_lock );
}
void
wrlock ( void )
{
pthread_rwlock_wrlock( &_lock );
}
void
unlock ( void )
{
pthread_rwlock_unlock( &_lock );
}
int
tryrdlock ( void )
{
return pthread_rwlock_tryrdlock( &_lock );
}
int
trywrlock ( void )
{
return pthread_rwlock_trywrlock( &_lock );
}
};

View File

@ -555,9 +555,10 @@ Region::read ( sample_t *buf, nframes_t pos, nframes_t nframes, int channel ) co
{
const Range &r = _range;
/* do nothing if we aren't covered by this frame range */
const nframes_t length = r.end - r.start;
if ( ! ( pos > r.offset + length || r.offset + length < pos ) )
/* do nothing if we aren't covered by this frame range */
if ( pos > r.offset + length || pos + nframes < r.offset )
return 0;
/* calculate offsets into file and sample buffer */
@ -576,11 +577,11 @@ Region::read ( sample_t *buf, nframes_t pos, nframes_t nframes, int channel ) co
sofs = pos - r.offset;
}
if ( sofs > nframes )
if ( ofs > nframes )
return 0;
const nframes_t start = ofs + r.start + sofs;
const nframes_t len = min( cnt, nframes - sofs );
const nframes_t len = min( cnt, nframes - ofs );
const nframes_t end = start + len;
if ( len == 0 )

View File

@ -24,8 +24,8 @@
#include "Control_Track.H"
#include <FL/Fl_Scrollbar.H>
#include <FL/Fl_Image.H>
#include <FL/Fl_RGB_Image.H> // needed for alpha blending
// #include <FL/Fl_Image.H>
// #include <FL/Fl_RGB_Image.H> // needed for alpha blending
#include "Track_Header.H"
@ -153,7 +153,7 @@ Timeline::Timeline ( int X, int Y, int W, int H, const char* L ) : Fl_Overlay_Wi
o->type( Fl_Pack::VERTICAL );
o->spacing( 0 );
for ( int i = 8; 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, 30 );

View File

@ -79,9 +79,9 @@ struct Rectangle
class Engine;
#include "Mutex.H"
#include "RWLock.H"
class Timeline : public Fl_Overlay_Window, public Mutex
class Timeline : public Fl_Overlay_Window, public RWLock
{
static void draw_clip ( void * v, int X, int Y, int W, int H );

View File

@ -19,6 +19,9 @@
#include "Track_Header.H"
#include "Disk_Stream.H"
#include "Engine.H"
void
Track_Header::cb_input_field ( Fl_Widget *w, void *v )
{
@ -85,6 +88,7 @@ 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 ) :
Fl_Group ( X, Y, W, H, L )
@ -96,7 +100,9 @@ Track_Header::Track_Header ( int X, int Y, int W, int H, const char *L ) :
_show_all_takes = false;
_size = 1;
// diskstream = new Disk_Stream( this );
output.push_back( Port( "foo" ) );
diskstream = new Disk_Stream( this, engine->frame_rate(), engine->nframes(), 1 );
Fl_Group::size( w(), height() );
@ -284,8 +290,12 @@ Track_Header::add_control( Track *t )
/* Engine */
/**********/
/* THREAD: RT */
nframes_t
Track_Header::process ( nframes_t nframes )
{
return track()->process( nframes );
if ( diskstream )
return diskstream->process( nframes );
else
return 0;
}

View File

@ -51,6 +51,9 @@
#include "Track_Header.H"
// #include "const.h"
#include "Engine.H"
Engine *engine;
Timeline *timeline;
void cb_undo ( Fl_Widget *w, void *v )
@ -78,6 +81,10 @@ main ( int argc, char **argv )
Loggable::register_create( "Control_Point", &Control_Point::create );
Loggable::register_create( "Track_Header", &Track_Header::create );
/* we don't really need a pointer for this */
engine = new Engine;
engine->init();
timeline = new Timeline( 0, 24, main_window->w(), main_window->h() - 24, "Timeline" );
Fl_Button *o = new Fl_Button( 0, 0, 50, 24, "undo" );