@@ -25,6 +25,8 @@ | |||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <string.h> | #include <string.h> | ||||
#include <assert.h> | |||||
Audio_File_SF * | Audio_File_SF * | ||||
Audio_File_SF::from_file ( const char *filename ) | 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 ); | return sf_readf_float( _in, buf, len ); | ||||
else | 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 ]; | sample_t *tmp = new sample_t[ len * _channels ]; | ||||
nframes_t rlen = sf_readf_float( _in, tmp, len ); | 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 | nframes_t | ||||
Audio_File_SF::read ( sample_t *buf, int channel, nframes_t start, nframes_t end ) | Audio_File_SF::read ( sample_t *buf, int channel, nframes_t start, nframes_t end ) | ||||
{ | { | ||||
assert( end > start ); | |||||
open(); | open(); | ||||
seek( start ); | seek( start ); | ||||
@@ -99,7 +99,8 @@ Audio_Track::handle ( int m ) | |||||
return 0; | 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(); | redraw(); | ||||
return 1; | 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 ]; | sample_t *cbuf = new sample_t[ nframes ]; | ||||
/* quick and dirty--let the regions figure out coverage for themselves */ | /* 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); | 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 */ | /* FIXME: bogus */ | ||||
return nframes; | 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 ); | size_t bufsize = blocks * nframes * sizeof( sample_t ); | ||||
for ( int i = channels; i--; ) | for ( int i = channels; i--; ) | ||||
_rb[ i ] = jack_ringbuffer_create( bufsize ); | |||||
_rb.push_back( jack_ringbuffer_create( bufsize ) ); | |||||
sem_init( &_blocks, 0, blocks ); | sem_init( &_blocks, 0, blocks ); | ||||
@@ -97,10 +97,27 @@ Disk_Stream::io_thread ( void *arg ) | |||||
void | void | ||||
Disk_Stream::read_block ( sample_t *buf ) | 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() ) ) | if ( track()->play( buf, _frame, _nframes, channels() ) ) | ||||
_frame += _nframes; | _frame += _nframes; | ||||
else | else | ||||
/* error */; | /* error */; | ||||
timeline->unlock(); | |||||
} | } | ||||
/* THREAD: IO */ | /* THREAD: IO */ | ||||
@@ -119,6 +136,8 @@ Disk_Stream::io_thread ( void ) | |||||
while ( wait_for_block() ) | while ( wait_for_block() ) | ||||
{ | { | ||||
// printf( "IO: RT thread is ready for more data...\n" ); | |||||
read_block( buf ); | read_block( buf ); | ||||
/* deinterleave the buffer and stuff it into the per-channel ringbuffers */ | /* deinterleave the buffer and stuff it into the per-channel ringbuffers */ | ||||
@@ -140,8 +159,8 @@ Disk_Stream::io_thread ( void ) | |||||
/* THREAD: RT */ | /* THREAD: RT */ | ||||
/** take a block from the ringbuffers and send it out the track's | /** take a block from the ringbuffers and send it out the track's | ||||
* ports */ | * ports */ | ||||
void | |||||
Disk_Stream::process ( void ) | |||||
nframes_t | |||||
Disk_Stream::process ( nframes_t nframes ) | |||||
{ | { | ||||
const size_t block_size = _nframes * sizeof( sample_t ); | const size_t block_size = _nframes * sizeof( sample_t ); | ||||
@@ -154,4 +173,7 @@ Disk_Stream::process ( void ) | |||||
} | } | ||||
block_processed(); | block_processed(); | ||||
/* FIXME: bogus */ | |||||
return nframes; | |||||
} | } |
@@ -86,6 +86,6 @@ public: | |||||
void run ( void ); | void run ( void ); | ||||
void read_block ( sample_t *buf ); | void read_block ( sample_t *buf ); | ||||
void io_thread ( void ); | 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 ( ) | Mutex ( ) | ||||
{ | { | ||||
// _lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; | // _lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; | ||||
pthread_mutex_init( &_lock, NULL ); | |||||
} | |||||
virtual ~Mutex ( ) | |||||
{ | |||||
pthread_mutex_destroy( &_lock ); | |||||
} | } | ||||
void | void | ||||
@@ -21,6 +21,8 @@ | |||||
#include <string.h> | #include <string.h> | ||||
#include "Engine.H" | |||||
/* nframes is the number of frames to buffer */ | /* nframes is the number of frames to buffer */ | ||||
Port::Port ( jack_port_t *port ) | Port::Port ( jack_port_t *port ) | ||||
{ | { | ||||
@@ -28,9 +30,17 @@ Port::Port ( jack_port_t *port ) | |||||
_name = jack_port_name( _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 ( ) | Port::~Port ( ) | ||||
{ | { | ||||
/* close port? */ | /* close port? */ | ||||
// jack_port_unregister( engine->client(), _port ); | |||||
} | } | ||||
void | void | ||||
@@ -33,6 +33,7 @@ class Port | |||||
public: | public: | ||||
Port ( jack_port_t *port ); | Port ( jack_port_t *port ); | ||||
Port ( const char *name ); | |||||
~Port ( ); | ~Port ( ); | ||||
bool connected ( void ) const { return jack_port_connected( _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; | const Range &r = _range; | ||||
/* do nothing if we aren't covered by this frame range */ | |||||
const nframes_t length = r.end - r.start; | 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; | return 0; | ||||
/* calculate offsets into file and sample buffer */ | /* 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; | sofs = pos - r.offset; | ||||
} | } | ||||
if ( sofs > nframes ) | |||||
if ( ofs > nframes ) | |||||
return 0; | return 0; | ||||
const nframes_t start = ofs + r.start + sofs; | 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; | const nframes_t end = start + len; | ||||
if ( len == 0 ) | if ( len == 0 ) | ||||
@@ -24,8 +24,8 @@ | |||||
#include "Control_Track.H" | #include "Control_Track.H" | ||||
#include <FL/Fl_Scrollbar.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" | #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->type( Fl_Pack::VERTICAL ); | ||||
o->spacing( 0 ); | 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, 75 ); | ||||
Track_Header *t = new Track_Header( 0, 0, W, 30 ); | Track_Header *t = new Track_Header( 0, 0, W, 30 ); | ||||
@@ -79,9 +79,9 @@ struct Rectangle | |||||
class Engine; | 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 ); | static void draw_clip ( void * v, int X, int Y, int W, int H ); | ||||
@@ -19,6 +19,9 @@ | |||||
#include "Track_Header.H" | #include "Track_Header.H" | ||||
#include "Disk_Stream.H" | |||||
#include "Engine.H" | |||||
void | void | ||||
Track_Header::cb_input_field ( Fl_Widget *w, void *v ) | 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 ) : | 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 ) | ||||
@@ -96,7 +100,9 @@ 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; | ||||
// 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() ); | Fl_Group::size( w(), height() ); | ||||
@@ -284,8 +290,12 @@ Track_Header::add_control( Track *t ) | |||||
/* Engine */ | /* Engine */ | ||||
/**********/ | /**********/ | ||||
/* THREAD: RT */ | |||||
nframes_t | nframes_t | ||||
Track_Header::process ( nframes_t nframes ) | 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 "Track_Header.H" | ||||
// #include "const.h" | // #include "const.h" | ||||
#include "Engine.H" | |||||
Engine *engine; | |||||
Timeline *timeline; | Timeline *timeline; | ||||
void cb_undo ( Fl_Widget *w, void *v ) | 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( "Control_Point", &Control_Point::create ); | ||||
Loggable::register_create( "Track_Header", &Track_Header::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" ); | timeline = new Timeline( 0, 24, main_window->w(), main_window->h() - 24, "Timeline" ); | ||||
Fl_Button *o = new Fl_Button( 0, 0, 50, 24, "undo" ); | Fl_Button *o = new Fl_Button( 0, 0, 50, 24, "undo" ); | ||||