1158 lines
25 KiB
C
1158 lines
25 KiB
C
|
||
/*******************************************************************************/
|
||
/* 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 "smf.H"
|
||
#include "phrase.H"
|
||
#include "pattern.H"
|
||
|
||
|
||
smf::smf ( void )
|
||
{
|
||
_name = NULL;
|
||
_pos = 0;
|
||
|
||
_fp = NULL;
|
||
|
||
_length = 0;
|
||
_length_pos = 0;
|
||
_num_tracks_pos = 0;
|
||
_tracks = 0;
|
||
_time = 0;
|
||
_tally = 0;
|
||
_cue = 0;
|
||
_track = 0;
|
||
}
|
||
|
||
smf::~smf ( void )
|
||
{
|
||
/* fill in the number of tracks */
|
||
if ( _num_tracks_pos )
|
||
{
|
||
fseek( _fp, _num_tracks_pos, SEEK_SET );
|
||
write_short( _tracks );
|
||
}
|
||
|
||
if ( _fp )
|
||
fclose( _fp );
|
||
|
||
if ( _name )
|
||
free( _name );
|
||
}
|
||
|
||
int
|
||
smf::open ( const char *name, int mode )
|
||
{
|
||
_name = strdup( name );
|
||
|
||
_mode = mode;
|
||
|
||
_fp = fopen( _name, mode == smf::WRITE ? "w" : "r" );
|
||
|
||
return _fp > 0;
|
||
}
|
||
|
||
/*************************/
|
||
/* Private bit twiddlers */
|
||
/*************************/
|
||
|
||
unsigned long
|
||
smf::read_long ( void )
|
||
{
|
||
byte_t buf[4];
|
||
unsigned long ret = 0;
|
||
|
||
read_bytes( buf, 4 );
|
||
|
||
ret += *(buf + 0) << 24;
|
||
ret += *(buf + 1) << 16;
|
||
ret += *(buf + 2) << 8;
|
||
ret += *(buf + 3);
|
||
|
||
return ret;
|
||
}
|
||
|
||
unsigned short
|
||
smf::read_short ( void )
|
||
{
|
||
byte_t buf[2];
|
||
unsigned short ret = 0;
|
||
|
||
read_bytes( buf, 2 );
|
||
|
||
ret += *(buf + 0) << 8;
|
||
ret += *(buf + 1);
|
||
|
||
return ret;
|
||
}
|
||
|
||
unsigned long
|
||
smf::read_var ( void )
|
||
{
|
||
unsigned long ret = 0;
|
||
unsigned char c;
|
||
|
||
/* while bit #7 is set */
|
||
while ( ( ( c = read_byte() ) & 0x80 ) != 0x00 )
|
||
{
|
||
|
||
/* shift ret 7 bits */
|
||
ret <<= 7;
|
||
/* add bits 0-6 */
|
||
ret += c & 0x7F;
|
||
}
|
||
|
||
/* bit was clear */
|
||
ret <<= 7;
|
||
ret += c & 0x7F;
|
||
|
||
return ret;
|
||
}
|
||
|
||
void
|
||
smf::read_bytes ( void *p, int l )
|
||
{
|
||
fread( p, l, 1, _fp );
|
||
|
||
_pos += l;
|
||
}
|
||
|
||
byte_t
|
||
smf::read_byte ( void )
|
||
{
|
||
byte_t b;
|
||
read_bytes( &b, 1 );
|
||
|
||
return b;
|
||
}
|
||
|
||
void
|
||
smf::write_var ( long var )
|
||
{
|
||
long buffer;
|
||
buffer = var & 0x7F;
|
||
|
||
/* we shift it right 7, if there is
|
||
still set bits, encode into buffer
|
||
in reverse order */
|
||
while ( ( var >>= 7) )
|
||
{
|
||
buffer <<= 8;
|
||
buffer |= ( var & 0x7F ) | 0x80;
|
||
}
|
||
|
||
for ( ;; )
|
||
{
|
||
write_byte( buffer );
|
||
|
||
if ( buffer & 0x80 )
|
||
buffer >>= 8;
|
||
else
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
void
|
||
smf::write_long ( unsigned long x )
|
||
{
|
||
byte_t buf[4];
|
||
|
||
buf[0] = ( x & 0xFF000000 ) >> 24;
|
||
buf[1] = ( x & 0x00FF0000 ) >> 16;
|
||
buf[2] = ( x & 0x0000FF00 ) >> 8;
|
||
buf[3] = x & 0x000000FF;
|
||
|
||
write_bytes( buf, 4 );
|
||
}
|
||
|
||
void
|
||
smf::write_ascii ( const char *buf )
|
||
{
|
||
if ( strlen( buf ) != 4 )
|
||
ASSERTION( "invalid MIDI value" );
|
||
|
||
write_bytes( (void *)buf, 4 );
|
||
}
|
||
|
||
void
|
||
smf::write_short ( unsigned short x )
|
||
{
|
||
byte_t buf[2];
|
||
|
||
buf[0] = (x & 0xFF00 ) >> 8;
|
||
buf[1] = x & 0x00FF;
|
||
|
||
write_bytes( buf, 2 );
|
||
}
|
||
|
||
void
|
||
smf::write_byte ( byte_t b )
|
||
{
|
||
write_bytes( &b, 1 );
|
||
}
|
||
|
||
|
||
void
|
||
smf::write_bytes ( const void *p, size_t l )
|
||
{
|
||
fwrite( p, l, 1, _fp );
|
||
_tally += l;
|
||
}
|
||
|
||
|
||
|
||
/*************************/
|
||
/* Read and write tracks */
|
||
/*************************/
|
||
|
||
/* write event /e/ to the currently open file (should only be used in a track)
|
||
if /cue/ is true, transform a notes-on/off into cue messages */
|
||
void
|
||
smf::write_event ( const midievent *e )
|
||
{
|
||
tick_t ts = e->timestamp();
|
||
tick_t delta = ts - _time;
|
||
_time = ts;
|
||
|
||
write_var( delta );
|
||
|
||
if ( _cue && (e->is_note_off() || e->is_note_on() ) )
|
||
{
|
||
/* begin cue message */
|
||
write_byte( 0xF0 ); /* sysex */
|
||
|
||
write_var( 7 ); /* length of this message */
|
||
|
||
static byte_t data[] = { 0x7F, /* MTC */
|
||
0, /* id */
|
||
0x05 }; /* cue message */
|
||
|
||
write_bytes( data, sizeof( data ) );
|
||
|
||
write_byte( e->opcode() == event::NOTE_ON ? 0x05 : 0x06 );
|
||
write_short( e->note() );
|
||
|
||
/* terminate */
|
||
write_byte( 0xF7 );
|
||
|
||
_status = 0;
|
||
}
|
||
else
|
||
{
|
||
byte_t buf[4];
|
||
|
||
int l = e->size();
|
||
|
||
midievent me = *e;
|
||
|
||
if ( me.opcode() == event::NOTE_OFF )
|
||
{
|
||
me.opcode( event::NOTE_ON );
|
||
me.note_velocity( 0 );
|
||
}
|
||
|
||
me.raw( buf, l );
|
||
|
||
/* write with running status */
|
||
if ( buf[0] != _status )
|
||
{
|
||
write_bytes( buf, l );
|
||
_status = buf[0];
|
||
}
|
||
else
|
||
write_bytes( buf + 1, l - 1 );
|
||
|
||
}
|
||
}
|
||
|
||
void
|
||
smf::write_header ( int fmt )
|
||
{
|
||
write_ascii( "MThd" );
|
||
write_long( 6 ); /* Always 6 bytes of header */
|
||
|
||
_format = fmt;
|
||
|
||
write_short( fmt ); /* format, SMF-0 for 1 track SMF-2 for more */
|
||
|
||
_num_tracks_pos = ftell( _fp );
|
||
_tracks = 0;
|
||
|
||
write_short( 0xDEAF );
|
||
|
||
write_short( PPQN );
|
||
}
|
||
|
||
/* start a new MIDI 'chunk', /id/ is 4 letters of ASCII */
|
||
void
|
||
smf::open_chunk ( const char *id )
|
||
{
|
||
if ( _length_pos )
|
||
ASSERTION( "chunks cannot be nested!" );
|
||
|
||
write_ascii( id );
|
||
|
||
/* reset track length counter */
|
||
_length_pos = ftell( _fp );
|
||
|
||
write_long( 0xBEEFCAFE ); /* length, this has to be filled in at track end! */
|
||
|
||
_tally = 0;
|
||
_time = 0;
|
||
}
|
||
|
||
void
|
||
smf::close_chunk ( void )
|
||
{
|
||
/* fill in track length */
|
||
long here = ftell( _fp );
|
||
|
||
fseek( _fp, _length_pos, SEEK_SET );
|
||
|
||
write_long( _tally );
|
||
|
||
fseek( _fp, here, SEEK_SET );
|
||
|
||
/* cleanup */
|
||
_length_pos = 0;
|
||
_tally = 0;
|
||
}
|
||
|
||
void
|
||
smf::open_track ( const char *name, int num )
|
||
{
|
||
open_chunk( "MTrk" );
|
||
|
||
if ( _format == 2 && num >= 0 )
|
||
write_meta_event ( smf::SEQUENCE, num );
|
||
|
||
if ( name )
|
||
write_meta_event ( smf::NAME, name );
|
||
|
||
++_tracks;
|
||
// FIXME: write time signature here
|
||
}
|
||
|
||
void
|
||
smf::close_track ( tick_t length )
|
||
{
|
||
/* end */
|
||
write_meta_event( smf::END, length ? length - _time : 0 );
|
||
|
||
_cue = 0;
|
||
|
||
close_chunk();
|
||
}
|
||
|
||
void
|
||
smf::write_meta_event ( byte_t type, int n )
|
||
{
|
||
write_var( type == smf::END ? n : 0 ); /* delta time */
|
||
write_short( 0xFF00 + type );
|
||
|
||
/* write length bytes */
|
||
switch ( type )
|
||
{
|
||
case smf::TEMPO:
|
||
write_byte( 3 );
|
||
// FIXME:
|
||
break;
|
||
case smf::SEQUENCE:
|
||
write_byte( 2 );
|
||
write_short( n );
|
||
break;
|
||
case smf::CHANNEL:
|
||
case smf::PORT:
|
||
write_byte( 1 );
|
||
write_byte( n );
|
||
break;
|
||
case smf::END:
|
||
write_byte( 0x00 );
|
||
break;
|
||
case smf::PROPRIETARY:
|
||
// length
|
||
write_var( n );
|
||
break;
|
||
// FIXME: handle time sig, key sig, proprietary
|
||
}
|
||
|
||
}
|
||
|
||
void
|
||
smf::write_meta_event ( byte_t type, const char *s )
|
||
{
|
||
write_var( 0 );
|
||
write_short( 0xFF00 + type );
|
||
|
||
switch ( type )
|
||
{
|
||
case smf::TEXT:
|
||
case smf::NAME:
|
||
case smf::INSTRUMENT:
|
||
case smf::COPYRIGHT:
|
||
case smf::LYRIC:
|
||
case smf::MARKER:
|
||
case smf::CUEPOINT:
|
||
{
|
||
int l = strlen( s );
|
||
|
||
write_var( l );
|
||
|
||
write_bytes( s, l );
|
||
|
||
break;
|
||
}
|
||
default:
|
||
ASSERTION( "event type does not take text!" );
|
||
break;
|
||
}
|
||
}
|
||
|
||
/** write song gloabl info (only used on playlist track) */
|
||
void
|
||
smf::write_song_info ( int mode, int phrases, int patterns, const char *name, const char *notes )
|
||
{
|
||
write_meta_event( smf::PROPRIETARY, 5 + (4 * 2) /* length */ );
|
||
|
||
write_ascii( "Non!" );
|
||
|
||
write_byte( mode );
|
||
write_long( phrases );
|
||
write_long( patterns );
|
||
|
||
if ( name )
|
||
write_meta_event( smf::NAME, name );
|
||
|
||
write_meta_event( smf::TEXT, ":: Created by the Non-Seqeuncer" );
|
||
|
||
if ( notes )
|
||
write_meta_event( smf::TEXT, notes );
|
||
}
|
||
|
||
void
|
||
smf::write_phrase_info ( const phrase *p )
|
||
{
|
||
if ( p->notes() )
|
||
write_meta_event( smf::TEXT, p->notes() );
|
||
|
||
char *s = p->viewport.dump();
|
||
|
||
char pat[156];
|
||
snprintf( pat, sizeof( pat ), "Non: xywh=%s",
|
||
s );
|
||
|
||
free( s );
|
||
|
||
write_meta_event( smf::PROPRIETARY, strlen( pat ) );
|
||
write_bytes( pat, strlen( pat ) );
|
||
}
|
||
|
||
/** write proprietary pattern info meta event */
|
||
void
|
||
smf::write_pattern_info ( const pattern *p )
|
||
{
|
||
write_meta_event( smf::PORT, p->port() );
|
||
|
||
char pat[256];
|
||
|
||
snprintf( pat, sizeof( pat ), "%s: %s", p->mapping.type(), p->mapping.name() );
|
||
|
||
write_meta_event( smf::INSTRUMENT, pat );
|
||
|
||
if ( p->notes() )
|
||
write_meta_event( smf::TEXT, p->notes() );
|
||
|
||
char *s = p->viewport.dump();
|
||
|
||
snprintf( pat, sizeof( pat ), "Non: xywh=%s, ppqn=%d, key=%d, note=%d, mode=%d",
|
||
s, p->ppqn(), p->mapping.key(), p->note(), p->mode() );
|
||
|
||
free( s );
|
||
|
||
write_meta_event( smf::PROPRIETARY, strlen( pat ) );
|
||
write_bytes( pat, strlen( pat ) );
|
||
}
|
||
|
||
/* turn on note->cue translation for this track */
|
||
void
|
||
smf::cue ( bool b )
|
||
{
|
||
_cue = b;
|
||
}
|
||
|
||
|
||
/**********/
|
||
/* Reader */
|
||
/**********/
|
||
|
||
char *
|
||
smf::read_text ( void )
|
||
{
|
||
int l = read_var();
|
||
|
||
char *s = (char*) malloc( l + 1 );
|
||
|
||
read_bytes( s, l );
|
||
|
||
s[l] = '\0';
|
||
|
||
return s;
|
||
}
|
||
|
||
|
||
int
|
||
smf::read_header ( void )
|
||
{
|
||
char id[4];
|
||
read_bytes( id, 4 );
|
||
|
||
if ( strncmp( id, "MThd", 4 ) )
|
||
return 0;
|
||
|
||
if ( read_long() != 6 )
|
||
return 0;
|
||
|
||
_format = read_short();
|
||
_tracks = read_short();
|
||
_ppqn = read_short();
|
||
|
||
_pos = 0;
|
||
|
||
return 1;
|
||
}
|
||
|
||
void
|
||
smf::home ( void )
|
||
{
|
||
fseek( _fp, 14, SEEK_SET );
|
||
|
||
_track = 0;
|
||
_pos = 0;
|
||
_length = 0;
|
||
}
|
||
|
||
void
|
||
smf::skip ( size_t l )
|
||
{
|
||
fseek( _fp, l, SEEK_CUR );
|
||
_pos += l;
|
||
}
|
||
|
||
void
|
||
smf::backup ( size_t l )
|
||
{
|
||
skip( 0 - l );
|
||
}
|
||
|
||
char *
|
||
smf::read_track_name ( void )
|
||
{
|
||
int status;
|
||
long where = 0;
|
||
int num = 0;
|
||
|
||
for ( num = 0; ; ++num )
|
||
{
|
||
where = _pos;
|
||
|
||
read_var(); /* delta */
|
||
status = read_byte();
|
||
|
||
/* stop at first non meta-event */
|
||
if ( status != midievent::META )
|
||
break;
|
||
|
||
int opcode = read_byte();
|
||
|
||
switch ( opcode )
|
||
{
|
||
case smf::NAME:
|
||
return read_text();
|
||
case smf::TEXT:
|
||
return read_text();
|
||
default:
|
||
skip( read_var() );
|
||
}
|
||
}
|
||
|
||
backup( _pos - where );
|
||
|
||
return NULL;
|
||
}
|
||
|
||
/** read next Cue Point event on track */
|
||
char *
|
||
smf::read_cue_point ( void )
|
||
{
|
||
read_var(); /* delta */
|
||
|
||
int status = read_byte();
|
||
|
||
if ( status != midievent::META )
|
||
return NULL;
|
||
|
||
int opcode = read_byte();
|
||
|
||
if ( opcode != smf::CUEPOINT )
|
||
return NULL;
|
||
|
||
return read_text();
|
||
}
|
||
|
||
bool
|
||
smf::read_song_info ( int * mode, int * phrases, int *patterns, char **name, char **notes )
|
||
{
|
||
int status;
|
||
long where = 0;
|
||
int num = 0;
|
||
bool r = false;
|
||
*notes = NULL;
|
||
|
||
for ( num = 0; ; ++num )
|
||
{
|
||
where = _pos;
|
||
|
||
read_var(); /* delta */
|
||
status = read_byte();
|
||
|
||
/* stop at first non meta-event */
|
||
if ( status != midievent::META )
|
||
break;
|
||
|
||
int opcode = read_byte();
|
||
|
||
switch ( opcode )
|
||
{
|
||
case smf::PROPRIETARY:
|
||
{
|
||
int len = read_var();
|
||
|
||
if ( len < 5 + (2 * 4) )
|
||
return false;
|
||
|
||
char id[4];
|
||
read_bytes( id, 4 );
|
||
|
||
if ( strncmp( id, "Non!", 4 ) )
|
||
return false;
|
||
|
||
*mode = read_byte();
|
||
*phrases = read_long();
|
||
*patterns = read_long();
|
||
|
||
r = true;
|
||
|
||
break;
|
||
}
|
||
case smf::TEXT:
|
||
{
|
||
char *text = read_text();
|
||
|
||
if ( ! strncmp( text, "::", 2 ) )
|
||
free( text );
|
||
else
|
||
*notes = text;
|
||
|
||
break;
|
||
}
|
||
case smf::NAME:
|
||
*name = read_text();
|
||
break;
|
||
case smf::END:
|
||
goto done;
|
||
default:
|
||
goto semidone;
|
||
}
|
||
}
|
||
|
||
semidone:
|
||
|
||
backup( _pos - where );
|
||
|
||
done:
|
||
|
||
return r;
|
||
}
|
||
|
||
bool
|
||
smf::read_phrase_info ( phrase *p )
|
||
{
|
||
int status;
|
||
long where = 0;
|
||
int num = 0;
|
||
|
||
for ( num = 0; ; ++num )
|
||
{
|
||
where = _pos;
|
||
|
||
read_var(); /* delta */
|
||
status = read_byte();
|
||
|
||
/* stop at first non meta-event */
|
||
if ( status != midievent::META )
|
||
break;
|
||
|
||
int opcode = read_byte();
|
||
|
||
switch ( opcode )
|
||
{
|
||
case smf::SEQUENCE:
|
||
/* currently, this is ignored */
|
||
read_var();
|
||
read_short();
|
||
break;
|
||
case smf::NAME:
|
||
p->name( read_text() );
|
||
MESSAGE( "Track name: %s", p->name() );
|
||
break;
|
||
case smf::INSTRUMENT:
|
||
skip( read_var() );
|
||
break;
|
||
case smf::TEXT:
|
||
p->notes( read_text() );
|
||
break;
|
||
case smf::PROPRIETARY:
|
||
{
|
||
int l = read_var();
|
||
|
||
char *data = (char *) alloca( l ) + 1;
|
||
|
||
read_bytes( data, l );
|
||
|
||
data[l] = '\0';
|
||
|
||
char *s;
|
||
|
||
if ( 1 != sscanf( data, "Non: xywh=%a[0-9:]",
|
||
&s ) )
|
||
WARNING( "Invalid phrase info event" );
|
||
else
|
||
{
|
||
p->viewport.read( s );
|
||
free( s );
|
||
}
|
||
break;
|
||
}
|
||
case smf::END:
|
||
/* Track ends before any non meta-events... */
|
||
read_byte();
|
||
goto done;
|
||
default:
|
||
int l = read_var();
|
||
skip( l );
|
||
WARNING( "skipping unrecognized meta event %02X", opcode );
|
||
break;
|
||
}
|
||
}
|
||
|
||
backup( _pos - where );
|
||
|
||
done:
|
||
|
||
return num ? p : NULL;
|
||
}
|
||
|
||
/** inform pattern /p/ from meta-events at the beginning of the
|
||
current track */
|
||
bool
|
||
smf::read_pattern_info ( pattern *p )
|
||
{
|
||
int status;
|
||
long where = 0;
|
||
int num = 0;
|
||
|
||
bool name_set = false;
|
||
|
||
for ( num = 0; ; ++num )
|
||
{
|
||
where = _pos;
|
||
|
||
read_var(); /* delta */
|
||
status = read_byte();
|
||
|
||
/* stop at first non meta-event */
|
||
if ( status != midievent::META )
|
||
break;
|
||
|
||
int opcode = read_byte();
|
||
|
||
switch ( opcode )
|
||
{
|
||
case smf::SEQUENCE:
|
||
/* currently, this is ignored */
|
||
read_var();
|
||
read_short();
|
||
break;
|
||
case smf::NAME:
|
||
p->name( read_text() );
|
||
MESSAGE( "Track name: %s", p->name() );
|
||
name_set = true;
|
||
break;
|
||
case smf::INSTRUMENT:
|
||
{
|
||
char *s = read_text();
|
||
|
||
char pat[256];
|
||
|
||
if ( 1 == sscanf( s, "Instrument: %[^\n]", pat ) )
|
||
{
|
||
if ( ! p->mapping.open( Mapping::INSTRUMENT, pat ) )
|
||
{
|
||
p->mapping.open( Mapping::SCALE, "Chromatic" );
|
||
WARNING( "could not find instrument \"%s\"", pat );
|
||
}
|
||
}
|
||
else
|
||
if ( 1 == sscanf( s, "Scale: %[^\n]", pat ) )
|
||
{
|
||
if ( ! p->mapping.open( Mapping::SCALE, pat ) )
|
||
{
|
||
p->mapping.open( Mapping::SCALE, "Chromatic" );
|
||
WARNING( "could not find scale \"%s\"", pat );
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
case smf::PORT:
|
||
read_byte();
|
||
p->port( read_byte() );
|
||
break;
|
||
case smf::TEXT:
|
||
if ( ! name_set )
|
||
{
|
||
/* also accept TEXT event as name if no name was
|
||
provided--this is found in a number of older MIDI
|
||
files. */
|
||
p->name( read_text() );
|
||
name_set = true;
|
||
}
|
||
else
|
||
p->notes( read_text() );
|
||
break;
|
||
case smf::PROPRIETARY:
|
||
{
|
||
int l = read_var();
|
||
|
||
char *data = (char *) alloca( l ) + 1;
|
||
|
||
read_bytes( data, l );
|
||
|
||
data[l] = '\0';
|
||
|
||
int ppqn, key, note, mode;
|
||
char *s;
|
||
|
||
if ( 5 != sscanf( data, "Non: xywh=%a[0-9:], ppqn=%d, key=%d, note=%d, mode=%d",
|
||
&s, &ppqn, &key, ¬e, &mode ) )
|
||
WARNING( "Invalid pattern info event" );
|
||
else
|
||
{
|
||
p->viewport.read( s );
|
||
free( s );
|
||
|
||
p->ppqn( ppqn );
|
||
|
||
if ( key > 0 )
|
||
p->mapping.key( key );
|
||
|
||
p->note( note );
|
||
p->mode( mode );
|
||
}
|
||
break;
|
||
}
|
||
case smf::END:
|
||
/* Track ends before any non meta-events... */
|
||
read_byte();
|
||
goto done;
|
||
default:
|
||
int l = read_var();
|
||
skip( l );
|
||
WARNING( "skipping unrecognized meta event %02X", opcode );
|
||
break;
|
||
}
|
||
}
|
||
|
||
backup( _pos - where );
|
||
|
||
done:
|
||
|
||
return num ? p : NULL;
|
||
}
|
||
|
||
int
|
||
smf::next_track ( void )
|
||
{
|
||
/* first, skip to the end of the track we're on, if any */
|
||
if ( _length )
|
||
skip( _length - _pos );
|
||
|
||
while ( ! feof( _fp ) && _track < _tracks )
|
||
{
|
||
char id[4];
|
||
read_bytes( id, 4 );
|
||
_length = read_long();
|
||
|
||
if ( strncmp( id, "MTrk", 4 ) )
|
||
{
|
||
WARNING( "skipping unrecognized chunk \"%s\"", id );
|
||
/* not a track chunk */
|
||
skip( _length );
|
||
continue;
|
||
}
|
||
|
||
_pos = 0;
|
||
++_track;
|
||
return 1;
|
||
}
|
||
|
||
return _length = _pos = 0;
|
||
}
|
||
|
||
|
||
/** locate track number /n/ */
|
||
bool
|
||
smf::seek_track ( int n )
|
||
{
|
||
home();
|
||
|
||
if ( n >= _tracks )
|
||
return false;
|
||
|
||
for ( int i = 0; next_track(); ++i )
|
||
if ( i == n )
|
||
break;
|
||
|
||
return true;
|
||
}
|
||
|
||
char **
|
||
smf::track_listing ( void )
|
||
{
|
||
if ( _pos != 0 )
|
||
ASSERTION( "attempt to get track listing while in the middle of reading a track." );
|
||
|
||
char **sa = (char**)malloc( sizeof( char* ) * (_tracks + 1) );
|
||
int i;
|
||
|
||
long where = ftell( _fp );
|
||
|
||
for ( i = 0; next_track(); ++i )
|
||
{
|
||
sa[i] = read_track_name();
|
||
sa[i] = sa[i] ? sa[i] : strdup( "<Unnamed>" );
|
||
}
|
||
|
||
sa[i] = NULL;
|
||
|
||
/* go back to where we started */
|
||
fseek( _fp, where, SEEK_SET );
|
||
_pos = 0;
|
||
|
||
return sa;
|
||
}
|
||
|
||
/* print track list for file /name/ */
|
||
void
|
||
smf::print_track_listing ( const char *name )
|
||
{
|
||
smf f;
|
||
|
||
f.open( name, smf::READ );
|
||
|
||
f.read_header();
|
||
|
||
char **sa = f.track_listing();
|
||
|
||
char *s;
|
||
for ( int i = 0; (s = sa[i]); ++i )
|
||
printf( "Track %3d: \"%s\"\n", i, s );
|
||
}
|
||
|
||
/** read all remaining events in current track and return them in a list */
|
||
list <midievent> *
|
||
smf::read_track_events ( tick_t *length )
|
||
{
|
||
list <midievent> *events = new list <midievent>;
|
||
event e;
|
||
|
||
*length = 0;
|
||
|
||
byte_t oldstatus = -1;
|
||
tick_t time = 0;
|
||
tick_t tick = 0;
|
||
tick_t delta;
|
||
|
||
while ( _pos < _length )
|
||
{
|
||
byte_t data[3];
|
||
|
||
delta = read_var();
|
||
|
||
int status = read_byte();
|
||
|
||
if ( ! (status & 0x80) )
|
||
{
|
||
backup( 1 );
|
||
status = oldstatus;
|
||
}
|
||
else
|
||
oldstatus = status;
|
||
|
||
time += delta;
|
||
tick = (time * PPQN) / _ppqn;
|
||
|
||
e.timestamp( tick );
|
||
|
||
int opcode = status & 0xF0;
|
||
|
||
// e.status( opcode );
|
||
e.status( status );
|
||
|
||
switch ( opcode )
|
||
{
|
||
case event::NOTE_OFF:
|
||
case event::NOTE_ON:
|
||
case event::AFTERTOUCH:
|
||
case event::CONTROL_CHANGE:
|
||
case event::PITCH_WHEEL:
|
||
|
||
read_bytes( data, 2 );
|
||
|
||
/* handle note off, vel 0 */
|
||
if ( opcode == event::NOTE_ON && 0 == data[1] )
|
||
{
|
||
e.opcode( event::NOTE_OFF );
|
||
data[1] = 127;
|
||
}
|
||
|
||
e.data( data[0], data[1] );
|
||
|
||
events->push_back( e );
|
||
|
||
/* TODO: set MIDI channel here */
|
||
break;
|
||
case event::PROGRAM_CHANGE:
|
||
case event::CHANNEL_PRESSURE:
|
||
|
||
data[0] = read_byte();
|
||
|
||
e.lsb( data[0] );
|
||
|
||
events->push_back( e );
|
||
break;
|
||
case 0xF0:
|
||
/* TODO: hanlde proprietary events? */
|
||
if ( midievent::META != status )
|
||
{
|
||
if ( 0xF0 == status )
|
||
{
|
||
/* looks like a sysex */
|
||
int l = read_var();
|
||
|
||
if ( l < 4 )
|
||
ASSERTION( "unrecognized message" );
|
||
|
||
byte_t *data = (byte_t *) alloca( 4 );
|
||
|
||
read_bytes( data, 4 );
|
||
|
||
l -= 4;
|
||
|
||
if ( data[0] == 0x7F &&
|
||
data[2] == 0x05 )
|
||
{
|
||
/* looks like a cue message! */
|
||
|
||
switch ( data[3] )
|
||
{
|
||
case 0x05:
|
||
/* start */
|
||
e.status( event::NOTE_ON );
|
||
e.note( read_short() );
|
||
events->push_back( e );
|
||
l -= 2;
|
||
break;
|
||
case 0x06:
|
||
/* stop */
|
||
e.status( event::NOTE_OFF );
|
||
e.note( read_short() );
|
||
events->push_back( e );
|
||
l -= 2;
|
||
break;
|
||
default:
|
||
ASSERTION( "unrecognized cue message" );
|
||
break;
|
||
}
|
||
}
|
||
|
||
MESSAGE( "converting MIDI cue to note-on/off n: %d", e.note() );
|
||
|
||
/* just in case */
|
||
skip( l );
|
||
}
|
||
else
|
||
{
|
||
WARNING( "unrecognized opcode %02X", status );
|
||
// FIXME: what now?
|
||
}
|
||
break;
|
||
}
|
||
|
||
opcode = read_byte();
|
||
|
||
switch ( opcode )
|
||
{
|
||
case smf::END: /* track end */
|
||
/* track extends until this event */
|
||
*length = tick;
|
||
|
||
if ( read_byte() )
|
||
WARNING( "corrupt MIDI file in track end" );
|
||
goto done;
|
||
break;
|
||
default:
|
||
WARNING( "unhandled meta-event %02X", opcode );
|
||
skip( read_var() );
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
done:
|
||
|
||
return events;
|
||
}
|
||
|
||
|
||
/**************************/
|
||
/* accessors (for reader) */
|
||
/**************************/
|
||
|
||
int
|
||
smf::format ( void ) const
|
||
{
|
||
return _format;
|
||
}
|
||
|
||
int
|
||
smf::tracks ( void ) const
|
||
{
|
||
return _tracks;
|
||
}
|