|
-
- /*******************************************************************************/
- /* 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:
- // FIXME: decode mapping;
- {
- char *s = read_text();
-
-
- char pat[256];
-
- if ( 1 == sscanf( s, "Instrument: %s", pat ) )
- {
- if ( ! p->mapping.open( Mapping::INSTRUMENT, pat ) )
- WARNING( "could not find instrument \"%s\"", pat );
- }
- else
- if ( 1 == sscanf( s, "Scale: %s", pat ) )
- {
- if ( ! p->mapping.open( Mapping::SCALE, pat ) )
- 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 );
- MESSAGE( "@ %lu", where );
-
- 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;
- }
|