From 30f33a3484be2d48d464db378ad81a6745a1bc0a Mon Sep 17 00:00:00 2001 From: Jonathan Moore Liles Date: Mon, 7 Apr 2008 02:29:30 -0500 Subject: [PATCH] Work on adding playback capability. --- Timeline/Disk_Stream.C | 96 ++++++++++++++++++++++++++++++++++++++++++ Timeline/Disk_Stream.H | 91 +++++++++++++++++++++++++++++++++++++++ Timeline/Port.C | 52 +++++++++++++++++++++++ Timeline/Port.H | 37 ++++++++++++++++ Timeline/Region.C | 65 +++++++++++++++++++++++++++- Timeline/Region.H | 2 + 6 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 Timeline/Disk_Stream.C create mode 100644 Timeline/Disk_Stream.H create mode 100644 Timeline/Port.C create mode 100644 Timeline/Port.H diff --git a/Timeline/Disk_Stream.C b/Timeline/Disk_Stream.C new file mode 100644 index 0000000..70d89b4 --- /dev/null +++ b/Timeline/Disk_Stream.C @@ -0,0 +1,96 @@ + +/*******************************************************************************/ +/* 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. */ +/*******************************************************************************/ + +static float seconds_to_buffer = 5.0f; + +/* 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 + thread (or vice-versa). */ +/* FIXME: handle termination of IO thread in destructor */ +/* FIXME: could all of this not simply be included in the Track_Header + class? */ + +/** start Disk_Stream thread */ +void +Disk_Stream::run ( void ) +{ + if ( pthread_create( 0, 0, &Disk_Stream::io_thread, this ) != 0 ) + /* error */; +} + +/* static wrapper */ +void +Disk_Stream::io_thread ( void *arg ) +{ + ((Disk_Stream*)arg)->io_thread(); +} + + +/* THREAD: IO */ +/** read a block of data from the track into /buf/ */ +Disk_Stream::read_block ( sample_t *buf ) +{ + if ( _th->track()->play( buf, _frame, _nframes, channels() ) ) + _frame += nframes; + else + /* error */; +} + +/* 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() ]; + /* buffer for a single channel */ + sample_t *cbuf = new sample_t[ _nframes ]; + + const size_t block_size = _nframes * sizeof( sample_t ); + + while ( wait_for_block() ) + { + read_block( buf ); + + /* deinterleave the buffer and stuff it into the per-channel ringbuffers */ + + for ( int i = channels(); i-- ) + { + int k = 0; + for ( int j = i; j < _nframes; j += channels() ) + cbuf[ k++ ] = buf[ j ]; + + jack_ringbuffer_write( _rb[ i ], cbuf, block_size ); + } + } + + delete[] buf; + delete[] cbuf; +} + +/* THREAD: RT */ +void +Disk_Stream::process ( nframes_t nframes ) +{ + _th->channels(); + + block_processed(); +} diff --git a/Timeline/Disk_Stream.H b/Timeline/Disk_Stream.H new file mode 100644 index 0000000..ef81856 --- /dev/null +++ b/Timeline/Disk_Stream.H @@ -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. */ +/*******************************************************************************/ + +#pragma once + +#include +#include + +class Disk_Stream +{ + + const Track_Header *_th; /* Track_Header we whould be playing */ + + nframes_t _nframes; + + vector _rb; + + // jack_ringbuffer_t *_rb; /* One interleaved ringbuffer for all channels */ + + sem_t _blocks; /* semaphore to wake the IO thread with */ + + int channels ( void ) const { return _rb.size(); } + +protected: + + void block_processed ( void ) { sem_post( &_blocks ); } + bool wait_for_block ( void ) { while ( sem_wait( &_work ) == EINTR ); return true; } + +public: + + static float seconds_to_buffer; + + Disk_Stream ( const Track_Header *th, float frame_rate, nframes_t nframes, int channels ) : _th( th ) + { + _frame = 0; + + const int blocks = frame_rate * seconds_to_buffer / nframes; + + _nframes = nframes; + + size_t bufsize = blocks * nframes * sizeof( sample_t ); + + for ( int i = channels(); i-- ) + _rb[ i ] = jack_ringbuffer_create( bufsize ); + + sem_init( &_blocks, 0, blocks ); + + run(); + } + + virtual ~Disk_Stream ( ) + { + _th = NULL; + + sem_destroy( &_blocks ); + + for ( int i = channels(); i-- ) + jack_ringbuffer_free( _rb[ i ] ); + } + + void + resize_buffer ( void ) + { + } + + + void + seek ( nframes_t frame ) + { + _frame = frame; + } + + void run ( void ); + +}; diff --git a/Timeline/Port.C b/Timeline/Port.C new file mode 100644 index 0000000..ab48582 --- /dev/null +++ b/Timeline/Port.C @@ -0,0 +1,52 @@ + +/*******************************************************************************/ +/* 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. */ +/*******************************************************************************/ + +/* RT/thread-safe interface to a single jack port. */ + +/* nframes is the number of frames to buffer */ +Port::Port ( jack_port_t *port, nframes_t nframes ) +{ + _port = port; + _rb = jack_ringbuffer_create( nframes * sizeof( stample_t ) ); + + _name = jack_port_name( _port ); +} + +Port::~Port ( ) +{ + jack_ringbuffer_free( _rb ); +} + +nframes_t +Port::write ( sample_t *buf, nframes_t nframes ) +{ + const size_t size = nframes * sizeof( sample_t ); + + return jack_ringbuffer_write( _rb, buf, size ) / sizeof( sample_t ); +} + +/* runs in the RT thread! */ +void +Port::process ( nframes_t nframes ) +{ + sample_t *buf = jack_port_get_buffer( _port, nframes ); + + /* FIXME: check size */ + jack_ringbuffer_read( _rb, buf, nframes ); +} diff --git a/Timeline/Port.H b/Timeline/Port.H new file mode 100644 index 0000000..3eddcb6 --- /dev/null +++ b/Timeline/Port.H @@ -0,0 +1,37 @@ + +/*******************************************************************************/ +/* 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. */ +/*******************************************************************************/ + +class Port +{ + jack_ringbuffer_t _rb; + jack_port_t *_port; + const char *_name; + +public: + + Port ( jack_port_t *port, nframes_t nframes ); + ~Port ( ); + + bool connected ( void ) const { return jack_port_connected( _port ); } + const char * name ( void ) const { return _name; } + + nframes_t write ( sample_t *buf, nframes_t nframes ); + void process ( nframes_t nframes ); + +}; diff --git a/Timeline/Region.C b/Timeline/Region.C index 4951acf..1e291d7 100644 --- a/Timeline/Region.C +++ b/Timeline/Region.C @@ -447,7 +447,7 @@ Region::draw ( int X, int Y, int W, int H ) int rw = timeline->ts_to_x( _r->end - _r->start ); - nframes_t end = _r->offset + ( _r->end - _r->start ); +// nframes_t end = _r->offset + ( _r->end - _r->start ); /* calculate waveform offset due to scrolling */ nframes_t offset = 0; @@ -537,3 +537,66 @@ Region::normalize ( void ) /* _scale = _clip->peaks( 0 )->normalization_factor( timeline->fpp(), _r->start, _r->end ); */ } + +/** read the overlapping part of /channel/ at /pos/ for /nframes/ of + this region into /buf/, where /pos/ is in timeline frames */ +/* this runs in the diskstream thread. */ +/* FIXME: it is far more efficient to read all the channels from a + multichannel source at once... But how should we handle the case of a + mismatch between the number of channels in this region's source and + the number of channels on the track/buffer this data is being read + for? Would it not be better to simply buffer and deinterlace the + frames in the Audio_File class instead, so that sequential requests + for different channels at the same position avoid hitting the disk + again? */ +/* FIXME: should fade-out/fade-ins not be handled here? */ +nframes_t +Region::read ( sample_t *buf, nframes_t pos, nframes_t nframes, int channel ) +{ + 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 ) ) + return 0; + + /* calculate offsets into file and sample buffer */ + + nframes_t sofs, ofs, cnt; + + if ( pos < r.offset ) + { + sofs = 0; + ofs = r.offset - pos; + cnt = nframes - ofs; + } + else + { + ofs = 0; + sofs = pos - r.offset; + } + + if ( sofs > nframes ) + return 0; + + const nframes_t start = ofs + r.start + sofs; + const nframes_t len = min( cnt, nframes - sofs ); + const nframes_t end = start + len; + + if ( len == 0 ) + return 0; + + /* now that we know how much and where to read, get on with it */ + + /* FIXME: seeking can be very expensive. Esp. with compressed + * formats. We should attempt to avoid it. But here or in the + * Audio_File class? */ + cnt = _clip->read( buf + ofs, channel, start, end ); + + /* apply gain */ + + for ( int i = cnt; i--; ) + buf[i] *= _scale; + + return cnt; +} diff --git a/Timeline/Region.H b/Timeline/Region.H index 7fee47c..11a47a8 100644 --- a/Timeline/Region.H +++ b/Timeline/Region.H @@ -206,5 +206,7 @@ public: void normalize ( void ); + nframes_t read ( sample_t *buf, nframes_t pos, nframes_t nframes, int channel ); + }; #endif