/*******************************************************************************/ /* Copyright (C) 2007-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 "grid.H" #include "common.h" #include "canvas.H" #include "non.H" #include "smf.H" using namespace MIDI; Grid::Grid ( void ) { _name = NULL; _notes = NULL; _number = 0; _height = 0; _rd = new data; _rw = NULL; // we need to initialize it here. data *d = (data *)_rd; _mode = 0; _locked = 0; d->length = 0; _bpb = 4; /* how many grid positions there are per beat */ _ppqn = 4; viewport.h = 32; viewport.w = 32; viewport.x = 0; viewport.y = 0; _playing = false; _suspend_update = false; _start = _end = _index = 0; } Grid::~Grid ( void ) { DMESSAGE( "deleting grid" ); if ( _name ) free( _name ); if ( _notes ) free( _notes ); if ( _rw ) delete _rw; if ( _rd ) delete _rd; } /* copy constructor */ Grid::Grid ( const Grid &rhs ) : sigc::trackable() { _rd = new data( *rhs._rd ); _rw = NULL; _name = rhs._name ? strdup( rhs._name ) : NULL; _notes = rhs._notes ? strdup( rhs._notes ) : NULL; _number = rhs._number; _height = rhs._height; _mode = 0; _locked = 0; _playing = false; _index = 0; _start = 0; _end = 0; _bpb = rhs._bpb; _ppqn = rhs._ppqn; viewport = rhs.viewport; } void Grid::lock ( void ) { if ( ! _locked++ ) _rw = new data( *_rd ); } void Grid::unlock ( void ) { if ( 0 == --_locked ) { _history.push_back( const_cast( _rd ) ); if ( _history.size() > MAX_UNDO + 1 ) { data *d = _history.front(); delete d; _history.pop_front(); } // swap the copy back in (atomically). _rd = (const data *)_rw; _rw = NULL; if ( ! _suspend_update ) signal_events_change(); } } event * Grid::_event ( int x, int y, bool write ) const { const data *d = const_cast< data * >(_rd); const event_list *r = write ? &_rw->events : &d->events; if ( r->empty() || x_to_ts( x ) > _rd->length ) return NULL; int note = y_to_note( y ); tick_t xt = x_to_ts( x ); for ( event *e = r->first(); e; e = e->next() ) { if ( ! e->is_note_on() ) continue; if ( e->note() != note ) continue; unsigned long ts = e->timestamp(); unsigned long l = 0; if ( e->linked() ) l = e->link()->timestamp() - ts; else WARNING( "found unlinked event... event list is corrupt." ); if ( xt >= ts && xt < ts + l ) // this is a little nasty return const_cast(e); } return NULL; } bool Grid::_delete ( int x, int y ) { event *e = _event ( x, y, true ); if ( e ) { if ( e->linked() ) _rw->events.remove( e->link() ); _rw->events.remove( e ); return true; } return false; } void Grid::clear ( void ) { lock(); _rw->events.clear(); unlock(); } void Grid::del ( int x, int y ) { lock(); _delete( x, y ); unlock(); } int Grid::next_note_x ( int x ) const { for ( const event *e = _rd->events.first(); e; e = e->next() ) if ( e->is_note_on() && (ts_to_x( e->timestamp() ) > (uint)x ) ) return ts_to_x( e->timestamp() ); return 0; } int Grid::prev_note_x ( int x ) const { for ( const event *e = _rd->events.last(); e; e = e->prev() ) if ( e->is_note_on() && (ts_to_x( e->timestamp() ) < (uint)x) ) return ts_to_x( e->timestamp() ); return 0; } void Grid::_fix_length ( void ) { tick_t beats = (unsigned long)(_rw->length / PPQN); tick_t rem = (unsigned long)_rw->length % PPQN; _rw->length = (rem ? (beats + 1) : beats) * PPQN; } /** Trim the length of the grid to the last event */ void Grid::trim ( void ) { lock(); event *e = _rw->events.last(); if ( e ) { tick_t ts = e->timestamp(); _rw->length = ts; _fix_length(); } unlock(); } void Grid::fit ( void ) { int hi, lo; _rd->events.hi_lo_note( &hi, &lo ); viewport.h = abs( hi - lo ) + 1; viewport.y = note_to_y( hi ); } /** Expand the length of the grid to the last event */ void Grid::expand ( void ) { lock(); event *e = _rw->events.last(); if ( e ) { tick_t ts = e->timestamp(); _rw->length = ts > _rw->length ? ts : _rw->length; _fix_length(); } unlock(); } /** returns true if there is a note event at x,y */ bool Grid::is_set ( int x, int y ) const { return _event( x, y, false ); } void Grid::put ( int x, int y, tick_t l, int velocity ) { int xl = ts_to_x( l ); tick_t ts = x_to_ts( x ); event *on = new event; event *off = new event; // Don't allow overlap (Why not?) if ( _event( x, y, false ) || _event( x + xl - 1, y, false ) ) return; DMESSAGE( "put %d,%d", x, y ); lock(); int note = y_to_note( y ); on->status( event::NOTE_ON ); on->note( note ); on->timestamp( ts ); on->note_velocity( velocity ); on->link( off ); off->status( event::NOTE_OFF ); off->note( note ); off->timestamp( ts + l ); off->note_velocity( velocity ); off->link( on ); _rw->events.insert( on ); _rw->events.insert( off ); expand(); unlock(); } // void // pattern::move ( int x, int y, int nx ) // { // event *e = _event( x, y, false ); // if ( e ) // e->timestamp( nx ); // } void Grid::move ( int x, int y, int nx, int ny ) { lock(); event *e = _event( x, y, true ); if ( e ) { DMESSAGE( "moving note" ); event *on = e, *off = e->link(); _rw->events.unlink( on ); _rw->events.unlink( off ); on->note( y_to_note( ny ) ); tick_t l = on->note_duration(); on->timestamp( x_to_ts( ny ) ); on->note_duration( l ); _rw->events.insert( off ); _rw->events.insert( on ); } unlock(); } void Grid::adj_velocity ( int x, int y, int n ) { lock(); event *e = _event( x, y, true ); if ( e ) { DMESSAGE( "adjusting velocity" ); { int v = e->note_velocity(); v += n; if ( v > 127 ) v = 127; e->note_velocity( v > 0 ? v : 1 ); } } unlock(); } void Grid::adj_duration ( int x, int y, int l ) { lock(); event *e = _event( x, y, true ); if ( e ) { DMESSAGE( "adjusting duration" ); { int v = ts_to_x( e->note_duration() ); v += l; e->note_duration( x_to_ts( v > 0 ? v : 1 ) ); _rw->events.sort( e->link() ); } } unlock(); } void Grid::set_duration ( int x, int y, int ex ) { if ( ex < 1 ) return; lock(); event *e = _event( x, y, true ); if ( e ) { DMESSAGE( "adjusting duration" ); e->note_duration( x_to_ts( ex ) ); _rw->events.sort( e->link() ); } unlock(); } void Grid::get_note_properties ( int x, int y, note_properties *p ) const { const event *e = _event( x, y, false ); e->get_note_properties( p ); p->start = p->start; p->duration = p->duration; p->note = note_to_y( p->note ); } /* void */ /* Grid::set_note_properties ( int x, int y, const note_properties *p ) */ /* { */ /* lock(); */ /* const event *e = _event( x, y, true ); */ /* e->set_note_properties( p ); */ /* unlock(); */ /* } */ /** if there's a note at grid coordinates x,y, then adjust them to the beginning of the note */ int Grid::get_start ( int *x, int *y ) const { const event *e = _event( *x, *y, false ); if ( e ) { *x = ts_to_x( e->timestamp() ); return 1; } else return 0; } void Grid::set_end ( int x, int y, int ex ) { lock(); event *e = _event( x, y, true ); if ( e ) { DMESSAGE( "adjusting duration" ); tick_t ts = x_to_ts( ex ); if ( ts > e->timestamp() && ts - e->timestamp() > x_to_ts( 1 ) ) { e->note_duration( ts - e->timestamp() ); _rw->events.sort( e->link() ); } } unlock(); } void Grid::toggle_select ( int x, int y ) { lock(); event *e = _event( x, y, true ); if ( e ) { if ( e->selected() ) e->deselect(); else e->select(); } unlock(); } /** insert /l/ ticks of time after /x/ */ void Grid::insert_time ( int l, int r ) { tick_t start = x_to_ts( l ); tick_t end = x_to_ts( r ); lock(); _rw->events.insert_time( start, end - start ); expand(); unlock(); } /** select all events in range (notes straddling the border will also be selected */ void Grid::select ( int l, int r ) { tick_t start = x_to_ts( l ); tick_t end = x_to_ts( r ); lock(); _rw->events.select( start, end ); unlock(); } /** select all (note) events in rectangle */ void Grid::select ( int l, int r, int t, int b ) { tick_t start = x_to_ts( l ); tick_t end = x_to_ts( r ); lock(); _rw->events.select( start, end, y_to_note( t) , y_to_note( b ) ); unlock(); } /** delete events from /x/ to /l/, compressing time. */ void Grid::delete_time ( int l, int r ) { tick_t start = x_to_ts( l ); tick_t end = x_to_ts( r ); lock(); _rw->events.delete_time( start, end ); unlock(); } void Grid::select_none ( void ) { lock(); _rw->events.select_none(); unlock(); } void Grid::select_all ( void ) { lock(); _rw->events.select_all(); unlock(); } void Grid::invert_selection ( void ) { lock(); _rw->events.invert_selection(); unlock(); } void Grid::delete_selected ( void ) { lock(); _rw->events.remove_selected(); unlock(); } void Grid::move_selected ( int l ) { long o = x_to_ts( abs( l ) ); if ( l < 0 ) o = 0 - o; lock(); // MESSAGE( "moving by %ld", o ); _rw->events.move_selected( o ); unlock(); } void Grid::crop ( int l, int r ) { lock(); if ( (uint)r < ts_to_x( _rw->length ) ) delete_time( r, ts_to_x( _rw->length ) ); if ( l > 0 ) delete_time( 0, l ); trim(); unlock(); } void Grid::crop ( int l, int r, int t, int b ) { lock(); _rw->events.push_selection(); select( l, r, t, b ); _rw->events.invert_selection(); _rw->events.remove_selected(); _rw->events.pop_selection(); crop( l, r ); unlock(); } void Grid::_relink ( void ) { _rw->events.relink(); } /* Dump the event list -- used by pattern / phrase dumppers */ void Grid::dump ( smf *f, int channel ) const { data *d = const_cast(_rd); midievent me; for ( event *e = d->events.first(); e; e = e->next() ) { me = *e; me.channel( channel ); f->write_event( &me ); } } void Grid::print ( void ) const { data *d = const_cast(_rd); for ( event *e = d->events.first(); e; e = e->next() ) e->print(); } /* */ /** Invoke /draw_note/ function for every note in the viewport */ void Grid::draw_notes ( draw_note_func_t draw_note, void *userdata ) const { /* int bx = viewport.x; */ /* int by = viewport.y; */ /* int bw = viewport.w + 100; /\* FIXME: hack *\/ */ /* int bh = viewport.h; */ /* const tick_t start = x_to_ts( bx ); */ /* const tick_t end = x_to_ts( bx + bw ); */ data *d = const_cast< data *>( _rd ); for ( const event *e = d->events.first(); e; e = e->next() ) { if ( ! e->is_note_on() ) continue; const tick_t ts = e->timestamp(); ASSERT( e->link(), "found a non-linked note" ); const tick_t tse = e->link()->timestamp(); /* if ( tse >= start && ts <= end ) */ draw_note( // ts_to_x( ts ), ts, note_to_y( e->note() ), tse - ts, e->note_velocity(), e->selected(), userdata ); } } /*******************************************/ /* Generic accessors -- boy C++ is verbose */ /*******************************************/ /** Returns the index (playhead) for this grid */ tick_t Grid::index ( void ) const { /* FIXME: considering the type of tick_t, we really need some kind of locking here to insure that this thread doesn't read _index while the RT thread is writing it. */ return _index; } bool Grid::playing ( void ) const { return _playing; } int Grid::height ( void ) const { return _height; } void Grid::height ( int h ) { _height = h; } tick_t Grid::length ( void ) const { return _rd->length; } void Grid::length ( tick_t l ) { lock(); _rw->length = l; unlock(); } int Grid::bars ( void ) const { return ts_to_x( _rd->length ) / (_ppqn * _bpb); } int Grid::beats ( void ) const { return ts_to_x( _rd->length ) / _ppqn; } int Grid::division ( void ) const { return _bpb * _ppqn; } int Grid::subdivision ( void ) const { return _ppqn; } int Grid::ppqn ( void ) const { return _ppqn; } /** set grid resolution to /n/, where 0 is 1/4 note, 1 is 1/8 note 2 is 1/16 note, etc. */ void Grid::resolution ( unsigned int n ) { /* if ( n < 4 ) */ /* ASSERTION( "bad resolution: %d", n ); */ // _ppqn = n / 4; _ppqn = n; DMESSAGE( "%d setting resolution to %d", n, _ppqn ); /* ensure that the correct number of bars are in the viewport */ viewport.w = _ppqn * _bpb * 2; signal_events_change(); signal_settings_change(); } int Grid::resolution ( void ) const { return _ppqn * 4; } int Grid::number ( void ) const { return _number; } void Grid::name ( char *s ) { if ( _name ) free ( _name ); _name = s; signal_settings_change(); } const char * Grid::name ( void ) const { return _name; } void Grid::notes ( char *s ) { if ( _notes ) free ( _notes ); _notes = s; signal_settings_change(); } char * Grid::notes ( void ) const { return _notes; } void Grid::mode ( int m ) { _mode = m; /* can't do this in RT thread, sorry. */ /// signal_settings_change(); } int Grid::mode ( void ) const { return _mode; } /** return a pointer to a copy of grid's event list in raw form */ event_list * Grid::events ( void ) const { data * d = const_cast< data * >( _rd ); return new event_list( d->events ); } /** replace event list with a copy of /el/ */ void Grid::events ( const event_list * el ) { lock(); _rw->events = *el; unlock(); }