@@ -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 ); | |||
@@ -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 ); */ | |||
/* } */ |
@@ -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; | |||
} |
@@ -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 ); | |||
}; |
@@ -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; | |||
} |
@@ -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; |
@@ -31,6 +31,12 @@ public: | |||
Mutex ( ) | |||
{ | |||
// _lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; | |||
pthread_mutex_init( &_lock, NULL ); | |||
} | |||
virtual ~Mutex ( ) | |||
{ | |||
pthread_mutex_destroy( &_lock ); | |||
} | |||
void | |||
@@ -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 | |||
@@ -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 ); } | |||
@@ -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 ); | |||
} | |||
}; |
@@ -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 ) | |||
@@ -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 ); | |||
@@ -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 ); | |||
@@ -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; | |||
} |
@@ -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" ); | |||