non/nonlib/MIDI/event_list.C

722 lines
15 KiB
C++
Raw Normal View History

/*******************************************************************************/
/* 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 "debug.h"
#include "event_list.H"
/* The operations we perform on event lists are clumsy with STL lists
and iterators so we have a custom doubly-linked list implementation
here for complete control */
namespace MIDI
{
#define RFOR_ALL( it ) for ( event *next, * it = _tail; it && ((next = it ->_prev), true) ; it = next )
#define FOR_ALL( it ) for ( event *next, * it = _head; it && ((next = it ->_next), true) ; it = next )
// #define FOR_ALL( e ) for ( event * e = _head; e; e = e ->_next )
#define FOR_SELECTED( e ) FOR_ALL( e ) if ( e ->selected() )
#define RFOR_SELECTED( e ) RFOR_ALL( e ) if ( e ->selected() )
event_list::event_list ( void )
{
_head = NULL;
_tail = NULL;
_size = 0;
}
event_list::~event_list ( void )
{
clear();
}
/* copy constructor */
event_list::event_list ( const event_list &el )
{
_copy( &el );
}
event_list &
event_list::operator= ( const event_list &rhs )
{
if ( this != &rhs )
{
clear();
_copy( &rhs );
}
return *this;
}
event_list &
event_list::operator= ( const list <midievent> &rhs )
{
clear();
for ( list <midievent>::const_iterator me = rhs.begin(); me != rhs.end(); me++ )
{
event *e = new event( *me );
_insert( NULL, e );
}
relink();
return *this;
}
/** allow indexing */
event *
event_list::operator[] ( unsigned int index )
{
unsigned int i = 0;
for ( event *e = _head; e; (e = e->_next), ++i )
if ( i == index )
return e;
// all else fails.
return _tail;
}
void
event_list::_copy ( const event_list *el )
{
if ( ! el->_head )
{
_head = _tail = NULL;
_size = 0;
return;
}
_head = new event( *(el->_head) );
_head->_prev = NULL;
event *p = _head;
for ( event *e = el->_head->_next; e; e = e->_next )
{
event *n = new event( *e );
n->_next = NULL;
p->_next = n;
n->_prev = p;
p = n;
}
_tail = p;
_size = el->_size;
relink();
}
/** insert event /n/ before event /o/ */
void
event_list::_insert ( event *o, event *n )
{
++_size;
if ( ! o )
{
n->_next = NULL;
n->_prev = _tail;
if ( _tail )
_tail->_next = n;
_tail = n;
if ( ! _head )
_head = n;
return;
}
event *t = o->_prev;
o->_prev = n;
n->_next = o;
n->_prev = t;
if ( ! t )
_head = n;
else
t->_next = n;
}
void
event_list::unlink ( event *e )
{
if ( e->_next )
e->_next->_prev = e->_prev;
else
_tail = e->_prev;
if ( e->_prev )
e->_prev->_next = e->_next;
else
_head = e->_next;
--_size;
}
void
event_list::clear ( void )
{
for ( event *e = _head; e ; )
{
event *n = e->_next;
delete e;
e = n;
}
_head = NULL;
_tail = NULL;
_size = 0;
}
void
event_list::mix ( event *ne )
{
FOR_ALL( e )
if ( *e == *ne )
{
/* already have an event like this, drop it */
if ( ne->linked() )
delete ne->link();
delete ne;
return;
}
insert( ne );
if ( ne->linked() )
insert( ne->link() );
}
/** remove elements from list /el/ to this list */
void
event_list::merge ( event_list *el )
{
event *n;
for ( event *e = el->_head; e; e = n )
{
n = e->_next;
el->unlink( e );
insert( e );
}
}
/** unlink event e */
void
event_list::remove ( event *e )
{
unlink( e );
delete e;
}
/** sorted insert /e/ */
void
event_list::insert ( event *e )
{
/* find the place to insert */
RFOR_ALL( i )
if ( *e >= *i )
{
_insert( i->_next, e );
return;
}
_insert( _head, e );
}
/** just append event without sorting */
void
event_list::append ( event *e )
{
_insert( NULL, e );
}
event *
event_list::first ( void ) const
{
return _head;
}
event *
event_list::last ( void ) const
{
return _tail;
}
/*************/
/* Selection */
/*************/
/** select all events from /start/ to /end/ inclusive */
void
event_list::select ( tick_t start, tick_t end )
{
FOR_ALL( e )
{
tick_t ts = e->timestamp();
/* don't count note offs exactly on start */
if ( ts == start && e->is_note_off() )
continue;
if ( ts >= start && ts < end )
e->select();
}
}
/** select note evenets from /start/ to /end/ within range /hi/ through /lo/ */
void
event_list::select ( tick_t start, tick_t end, int hi, int lo )
{
FOR_ALL( e )
{
tick_t ts = e->timestamp();
/* don't count note offs exactly on start */
if ( ! e->is_note_on() )
continue;
if ( ts >= start && ts < end &&
e->note() <= hi && e->note() >= lo )
e->select();
}
}
/** select ALL events */
void
event_list::select_all ( void )
{
FOR_ALL( e )
e->select();
}
void
event_list::select_none ( void )
{
FOR_ALL( e )
e->deselect();
}
void
event_list::invert_selection ( void )
{
FOR_ALL( e )
if ( ! e->is_note_off() )
{
if ( e->selected() )
e->deselect();
else
e->select();
}
}
/** remove all selected events */
void
event_list::remove_selected ( void )
{
FOR_SELECTED( e )
{
remove( e );
}
}
2013-05-08 00:24:54 +02:00
/** copy selected events into event list /el/ */
void
event_list::copy_selected ( event_list *el ) const
{
event *fi = first();
if ( ! fi )
return;
tick_t offset = fi->timestamp();
FOR_SELECTED( e )
{
event *nel = 0;
if ( e->linked() )
nel = new event(*e->link());
event *ne = new event(*e);
ne->timestamp( ne->timestamp() - offset );
if ( nel )
{
nel->link( ne );
ne->link( nel );
nel->timestamp( nel->timestamp() - offset );
}
el->mix(ne);
}
}
/** add events from list /el/ */
void
event_list::paste ( tick_t offset, const event_list *el )
{
event *n;
for ( event *e = el->_head; e; e = n )
{
n = e->_next;
event *ne = new event(*e);
ne->link( NULL );
ne->timestamp( ne->timestamp() + offset );
insert( ne );
}
relink();
}
/** transpose selected notes (ignoring other event types) by /n/ tones
* (may span octaves) */
void
event_list::transpose_selected ( int n )
{
FOR_SELECTED( e )
{
if ( e->is_note_on() )
e->note( e->note() + n );
}
}
/** change all notes of value /from/ to /to/ */
void
event_list::rewrite_selected ( int from, int to )
{
FOR_SELECTED( e )
{
if ( e->is_note_on() && e->note() == from )
e->note( to );
}
}
/** set velocity of selected notes to /v/ */
void
event_list::selected_velocity ( int v )
{
FOR_SELECTED( e )
{
if ( e->is_note_on() )
e->note_velocity( v );
}
}
/** get timestamp of earliest selected event */
tick_t
2013-05-08 00:24:54 +02:00
event_list::selection_min ( void ) const
{
FOR_SELECTED( e )
return e->timestamp();
return 0;
}
tick_t
2013-05-08 00:24:54 +02:00
event_list::selection_max ( void ) const
{
RFOR_SELECTED( e )
return e->timestamp();
return 0;
}
/** move selected events by offset /o/ */
void
2013-05-08 00:24:54 +02:00
event_list::nudge_selected ( long o )
{
if ( o < 0 )
if ( selection_min() < (tick_t)( 0 - o ) )
return;
if ( o < 0 )
{
FOR_SELECTED( e )
move( e, o );
}
else
{
RFOR_SELECTED( e )
move( e, o );
}
}
2013-05-08 00:24:54 +02:00
/** move block of selected events to tick /tick/ */
void
event_list::move_selected ( tick_t tick )
{
/* if ( o < 0 ) */
/* if ( selection_min() < (tick_t)( 0 - o ) ) */
/* return; */
tick_t min = selection_min();
tick_t offset = tick - min;
nudge_selected( offset );
/* FOR_SELECTED( e ) */
/* { */
/* e->timestamp( e->timestamp() + offset ); */
/* sort( e ); */
/* move( e, o ); */
/* } */
}
void
event_list::push_selection ( void )
{
FOR_ALL( e )
if ( e->_selected )
++e->_selected;
}
void
event_list::pop_selection ( void )
{
FOR_ALL( e )
if ( e->_selected )
--e->_selected;
}
/** verify that all note ons are linked to note offs */
bool
event_list::verify ( void ) const
{
FOR_ALL( e )
if ( e->is_note_on() && ! e->linked() )
return false;
return true;
}
/** link /e/ (a note on) with the next corresponding note off */
void
event_list::link ( event *on )
{
if ( ! on->is_note_on() )
return;
for ( event *off = on->_next; off; off = off->_next )
{
if ( off->linked() )
continue;
if ( off->is_note_off() &&
off->channel() == on->channel() &&
off->note() == on->note() )
{
on->link( off );
return;
}
}
WARNING( "no corresponding note_off found for note on, repairing" );
event *off = new event( *on );
off->opcode( event::NOTE_OFF );
on->link( off );
insert( off );
}
/** insert /l/ ticks of time at /start/ */
void
event_list::insert_time ( tick_t start, tick_t l )
{
FOR_ALL( e )
{
tick_t ts = e->timestamp();
if ( e->is_note_off() )
continue;
if ( ts >= start )
{
if ( e->is_note_on() )
{
/* only notes ENTIRELY WITHIN the range will be moved */
e->timestamp( ts + l );
e->link()->timestamp( e->link()->timestamp() + l );
}
else
e->timestamp( e->timestamp() + l );
}
}
sort();
}
/** delete events in range and close the gap */
void
event_list::delete_time ( tick_t start, tick_t end )
{
tick_t l = end - start;
push_selection();
select( start, end );
remove_selected();
pop_selection();
/* cut out the slack */
FOR_ALL( e )
{
tick_t ts = e->timestamp();
if ( ts >= end )
e->timestamp( ts - l );
}
}
/** link all note ons to subsequent note offs */
void
event_list::relink ( void )
{
/* clear links */
FOR_ALL( e )
e->link( NULL );
/* link */
FOR_ALL( on )
link( on );
if ( ! verify() )
FATAL( "event list failed verification" );
}
/** resort event /e/ */
void
event_list::sort ( event *e )
{
unlink( e );
insert( e );
}
/** resort entire list */
void
event_list::sort ( void )
{
event_list *temp = new event_list( );
_head = temp->_head;
_tail = temp->_tail;
FOR_ALL( n )
temp->insert( n );
temp->_head = NULL;
delete temp;
relink();
}
/** move event /e/ by /o/ ticks */
void
event_list::move ( event *e, long o )
{
e->timestamp( e->timestamp() + o );
sort( e );
}
bool
event_list::empty ( void ) const
{
return _head == NULL;
}
size_t
event_list::size ( void ) const
{
return _size;
}
void
event_list::_hi_lo ( bool sel, int *hi, int *lo ) const
{
*hi = 0;
*lo = 127;
FOR_ALL( e )
{
if ( sel && ! e->selected() )
continue;
if ( ! e->is_note_on() )
continue;
int n = e->note();
if ( n > *hi )
*hi = n;
if ( n < *lo )
*lo = n;
}
}
/** set /hi/ and /lo/ to the lowest and highest pitched note events in
* this list, respectively */
void
event_list::hi_lo_note ( int *hi, int *lo ) const
{
_hi_lo( false, hi, lo );
}
void
event_list::selected_hi_lo_note ( int *hi, int *lo ) const
{
_hi_lo( true, hi, lo );
}
}