non/pattern.C

685 lines
13 KiB
C
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*******************************************************************************/
/* 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 "pattern.H"
#include "non.H"
#include "common.h"
#include "smf.H"
#include "jack.H"
#include "transport.H"
event_list pattern::_recorded_events;
vector <pattern*> pattern::_patterns;
int pattern::_solo;
int pattern::_pattern_recording;
signal <void> pattern::signal_create_destroy;
pattern::pattern ( void )
{
viewport.h = 32;
viewport.w = 32;
_draw_shape = CIRCLE;
_channel = _port = 0;
_ppqn = 4;
_bpb = 4;
_note = 8;
int _bars = 2;
_triggered = false;
// we need to reinitalize this.
data *d = const_cast< data * >( _rd );
d->length = x_to_ts( _bpb * _ppqn * _bars );
mapping.open( Mapping::INSTRUMENT, "Default" );
_add();
char *s;
asprintf( &s, "Pattern %d", number() );
name( s );
}
void
pattern::_add ( void )
{
// keep track of all the patterns
pattern::_patterns.push_back( this );
_number = patterns();
signal_create_destroy();
}
pattern::~pattern ( void )
{
DEBUG( "deleting pattern %d", number() );
signal_create_destroy();
}
/* copy constructor */
pattern::pattern ( const pattern &rhs ) : Grid( rhs )
{
_note = rhs._note;
_port = rhs._port;
_channel = rhs._channel;
mapping = rhs.mapping;
_add();
}
pattern *
pattern::clone ( void )
{
return new pattern( *this );
}
/******************/
/* Static methods */
/******************/
int
pattern::solo ( void )
{
return pattern::_solo;
}
int
pattern::patterns ( void )
{
return pattern::_patterns.size();
}
// this is the static one
pattern *
pattern::pattern_by_number ( int n )
{
if ( n <= patterns() && n > 0 )
{
return pattern::_patterns[ n - 1 ];
}
return NULL;
}
/** delete all patterns in preparation for a reload */
void
pattern::reset ( void )
{
for ( int n = pattern::patterns(); n-- ; )
{
delete pattern::_patterns.back();
pattern::_patterns.pop_back();
}
}
void
pattern::record_event ( const midievent *me )
{
/* set the events aside in a dedicated list--the recording pattern
* will decide what to do with them the next time around the
* loop */
/* FIXME: how does the pattern decide when to loop? It seems
reasonable that /merge/ and /replace/ modes should be bound to
the previous pattern length, but what about "NEW" mode? Should it
just use this entire list as a new pattern (of whatever length)
when recording is halted? */
event *e = new event;
*e = *me;
pattern::_recorded_events.append( e );
record_mode_e mode = config.record_mode;
if ( mode == OVERWRITE || mode == LAYER )
{
pattern *p = pattern::recording();
if ( ! p->_cleared )
{
if ( mode == LAYER )
{
p->record_stop();
p = p->clone();
p->record( 0 );
}
p->clear();
p->_cleared = true;
}
mode = MERGE;
}
/* let's fill in the pattern 'live' in merge mode. looks a little
complicated because we have to wait for a note-off before it's
safe to insert */
if ( mode == MERGE || mode == NEW )
{
pattern *p = pattern::recording();
p->lock();
event_list *el = &pattern::_recorded_events;
if ( e->is_note_off() )
{
event *off = e;
for ( event *on = el->last(); on; on = on->prev() )
{
if ( on->is_note_on() &&
on->is_same_note( off ) )
// &&
// *on < *e )
{
el->unlink( on );
el->unlink( off );
tick_t duration = off->timestamp() - on->timestamp();
/* place within loop */
on->timestamp( ( on->timestamp() - p->_start ) % p->_rw->length );
on->link( off );
on->note_duration( duration );
p->_rw->events.mix( on );
break;
}
}
}
else
if ( ! e->is_note_on() )
{
// if ( ! filter )
e->timestamp( e->timestamp() % p->_rw->length );
el->unlink( e );
p->_rw->events.insert( e );
}
p->unlock();
}
}
pattern *
pattern::recording ( void )
{
return pattern::pattern_by_number( pattern::_pattern_recording );
}
/*******************/
/* Virtual Methods */
/*******************/
/* allows us to create a new pattern/phrase from a base class pointer */
pattern *
pattern::create ( void )
{
return new pattern;
}
pattern *
pattern::by_number ( int n ) const
{
return pattern::pattern_by_number( n );
}
void
pattern::put ( int x, int y, tick_t l )
{
l = l ? l : PPQN * 4 / _note;
Grid::put( x, y, l );
if ( ! transport.rolling )
{
/* echo note */
midievent e;
e.status( event::NOTE_ON );
e.channel( _channel );
e.timestamp( l );
e.note( y_to_note( y ) );
e.note_velocity( 64 );
midi_output_immediate_event ( _port, &e );
}
}
const char *
pattern::row_name ( int r ) const
{
return mapping.note_name( y_to_note( r ) );
}
void
pattern::draw_row_names ( Canvas *c ) const
{
for ( int y = 128; y-- ; )
c->draw_row_name( y, mapping.note_name( y_to_note( y ) ), mapping.velocity( y_to_note( y ) ) );
}
void
pattern::trigger ( tick_t start, tick_t end )
{
if ( start > end )
ASSERTION( "programming error: invalid loop trigger! (%lu-%lu)", start, end );
_start = start;
_end = end;
_index = 0;
}
void
pattern::stop ( void ) const
{
_playing = false;
_start = 0;
_end = 0;
_index = 0;
}
void
pattern::mode ( int n )
{
if ( song.play_mode == TRIGGER )
{
switch ( n )
{
case PLAY:
_triggered = true;
break;
case MUTE:
_triggered = false;
break;
}
return;
}
if ( n == SOLO )
{
if ( pattern::_solo )
((Grid*)pattern::pattern_by_number( pattern::_solo ))->mode( PLAY );
pattern::_solo = _number;
Grid::mode( SOLO );
}
else
{
if ( pattern::_solo == _number )
pattern::_solo = 0;
Grid::mode( n );
}
}
int
pattern::mode ( void ) const
{
if ( song.play_mode == TRIGGER )
{
if ( ! _triggered )
return MUTE;
else
return PLAY;
}
if ( pattern::_solo )
{
if ( pattern::_solo == _number )
return SOLO;
else
return MUTE;
}
else
return Grid::mode();
}
/* WARNING: runs in the RT thread! */
// output notes from /start/ to /end/ (absolute)
void
pattern::play ( tick_t start, tick_t end ) const
{
/* get our own copy of this pointer so UI thread can change it. */
const data *d = const_cast< const data * >(_rd);
if ( start > _end )
{
stop();
WARNING( "attempt to play a loop (pattern %d) that has ended (%lu, %lu)", number(), start, _end );
return;
}
else
if ( end < _start )
// not ready yet
return;
if ( start < _start )
start = _start;
if ( end > _end )
end = _end;
_playing = true;
// where we are in the absolute time
tick_t tick = start - _start;
int num_played = tick / d->length;
tick_t offset = _start + (d->length * num_played);
const event *e;
_index = tick % d->length;
if ( _index < end - start )
{
DEBUG( "Triggered pattern %d at tick %lu (ls: %lu, le: %lu, o: %lu)", number(), start, _start, _end, offset );
_cleared = false;
}
if ( mode() == MUTE )
return;
try_again:
// pattern is empty
if ( d->events.empty() )
goto done;
for ( e = d->events.first(); e; e = e->next() )
{
// MESSAGE( "s[%ld] -> t[%ld] : %ld, len %ld", start, end, e->timestamp(), _length ); // (*e).print();
tick_t ts = e->timestamp() + offset;
if ( ts >= end )
goto done;
if ( ts >= start )
{
midievent me = *e;
// MESSAGE( "timestamp %d, tick %d, ts - start == %lu", e->timestamp(), start,
// e->timestamp() - start);
/* set the channel */
me.channel( _channel );
/* set the in-cycle timestamp */
me.timestamp ( ts - start );
if ( me.is_note_on() )
{
mapping.translate( &me );
midi_output_event( _port, &me, 1 + e->note_duration() );
}
else
if ( me.is_note_off() )
midi_output_event( _port, &me, 0 );
else
/* any other event type */
midi_output_event( _port, &me );
}
}
// ran out of events, but there's still some loop left to play.
offset += d->length;
goto try_again;
DEBUG( "out of events, resetting to satisfy loop" );
done: ;
}
/* Import /track/ of /f/ as new pattern */
pattern *
pattern::import ( smf *f, int track )
{
if ( ! f->seek_track( track ) )
return NULL;
pattern *p = new pattern;
p->lock();
p->load( f );
/* file could have any notes in it... Use Chromatic scale to
ensure all are visible */
p->mapping.open( Mapping::SCALE, "Chromatic" );
p->unlock();
p->fit();
return p;
}
/** fill pattern from current track of /f/ */
void
pattern::load ( smf *f )
{
lock();
f->read_pattern_info( this );
tick_t len;
list <midievent> *e = f->read_track_events( &len );
/* set channel to channel of first event... */
if ( e->size() )
_channel = e->front().channel();
/* copy events into pattern */
_rw->events = *e;
delete e;
if ( len )
_rw->length = len;
unlock();
// print();
}
/** save (export) pattern to file /name/ */
void
pattern::save ( const char *name ) const
{
smf f;
/* open for writing */
f.open( name, smf::WRITE );
/* writing SMF 0 track */
f.write_header( 0 );
f.open_track( _name, _number );
Grid::dump( &f, _channel, true );
f.close_track( length() );
}
/** dump pattern as a track in an already open MIDI file */
void
pattern::dump ( smf *f ) const
{
f->open_track( _name, _number );
f->write_pattern_info( this );
Grid::dump( f, _channel, false );
f->close_track( length() );
}
void
pattern::randomize_row ( int y, int feel, float probability )
{
lock();
int l = PPQN * 4 / _note;
int bx = ts_to_x( _rw->length - l );
float *p = (float *)alloca( feel * sizeof( float ) );
float prob = probability;
for ( int i = 0; i < feel; i++ )
{
p[i] = prob;
// reduce probability as we move away from center
prob *= 0.5;
}
for ( int x = 0; x < bx; x++ )
{
float r = ((float)rand()) / RAND_MAX;
if ( p[ x % feel ] + r >= 1 )
put( x, y, l );
}
unlock();
}
/*************/
/* Recording */
/*************/
void
pattern::record ( int mode )
{
_recording = true;
pattern::_pattern_recording = _number;
}
void
pattern::record_stop ( void )
{
if ( ! _recording )
return;
_recording = false;
if ( config.record_mode == NEW )
trim();
pattern::_recorded_events.clear();
}
/*******************************/
/* Pattern specific accessors. */
/*******************************/
int
pattern::port ( void ) const
{
return _port;
}
void
pattern::port ( int p )
{
_port = p;
}
int
pattern::channel ( void ) const
{
return _channel;
}
void
pattern::channel ( int c )
{
_channel = c;
}
int
pattern::note ( void ) const
{
return _note;
}
void
pattern::note ( int n )
{
_note = n;
}
int
pattern::ppqn ( void ) const
{
return _ppqn;
}
void
pattern::ppqn ( int n )
{
_ppqn = n;
}
int
pattern::key ( void ) const
{
return mapping.key();
}
void
pattern::key ( int k )
{
mapping.key( k );
}