diff --git a/CMakeLists.txt b/CMakeLists.txt index 1427a78f2..b87cf2eb5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,7 +128,8 @@ IF(WANT_SDL) SET(LMMS_HAVE_SDL TRUE) SET(STATUS_SDL "OK") ELSE(SDL_FOUND) - SET(STATUS_SDL "not found, please install libsdl1.2-dev (or similiar)") + SET(STATUS_SDL "not found, please install libsdl1.2-dev (or similiar) " + "if your require SDL support") ENDIF(SDL_FOUND) ENDIF(WANT_SDL) IF(NOT LMMS_HAVE_SDL) @@ -148,7 +149,8 @@ IF(WANT_STK) SET(STATUS_STK "OK") ELSE(STK_FOUND) SET(STK_INCLUDE_DIR "") - SET(STATUS_STK "not found, please install libstk0-dev (or similiar)") + SET(STATUS_STK "not found, please install libstk0-dev (or similiar) " + "if your require the Vibed Instrument") ENDIF(STK_FOUND) ENDIF(WANT_STK) @@ -160,7 +162,8 @@ IF(WANT_PORTAUDIO) SET(LMMS_HAVE_PORTAUDIO TRUE) SET(STATUS_PORTAUDIO "OK") ELSE(PORTAUDIO_FOUND) - SET(STATUS_PORTAUDIO "not found, please install libportaudio-dev (or similiar, version >= 1.8)") + SET(STATUS_PORTAUDIO "not found, please install libportaudio-dev (or similiar, version >= 1.8) " + "if your require Portaudio support") ENDIF(PORTAUDIO_FOUND) ENDIF(WANT_PORTAUDIO) IF(NOT LMMS_HAVE_PORTAUDIO) @@ -176,7 +179,8 @@ IF(WANT_PULSEAUDIO) SET(LMMS_HAVE_PULSEAUDIO TRUE) SET(STATUS_PULSEAUDIO "OK") ELSE(PULSEAUDIO_FOUND) - SET(STATUS_PULSEAUDIO "not found, please install libpulse-dev (or similiar)") + SET(STATUS_PULSEAUDIO "not found, please install libpulse-dev (or similiar) " + "if your require Portaudio support") ENDIF(PULSEAUDIO_FOUND) ENDIF(WANT_PULSEAUDIO) IF(NOT LMMS_HAVE_PULSEAUDIO) @@ -192,7 +196,8 @@ IF(WANT_OGGVORBIS) SET(LMMS_HAVE_OGGVORBIS TRUE) SET(STATUS_OGGVORBIS "OK") ELSE(OGGVORBIS_FOUND) - SET(STATUS_OGGVORBIS "not found, please install libogg-dev and libvorbis-dev (or similiar)") + SET(STATUS_OGGVORBIS "not found, libogg-dev and libvorbis-dev (or similiar) " + "is highly recommended") ENDIF(OGGVORBIS_FOUND) ENDIF(WANT_OGGVORBIS) @@ -214,7 +219,8 @@ IF(WANT_ALSA) SET(LMMS_HAVE_ALSA TRUE) SET(STATUS_ALSA "OK") ELSE(ALSA_FOUND) - SET(STATUS_ALSA "not found, please install libasound2-dev (or similiar)") + SET(STATUS_ALSA "not found, please install libasound2-dev (or similiar) " + "if you require ALSA support") ENDIF(ALSA_FOUND) ENDIF(WANT_ALSA) IF(NOT LMMS_HAVE_ALSA) @@ -229,7 +235,8 @@ IF(WANT_JACK) SET(LMMS_HAVE_JACK TRUE) SET(STATUS_JACK "OK") ELSE(JACK_FOUND) - SET(STATUS_JACK "not found, please install libjack0.100.0-dev (or similiar)") + SET(STATUS_JACK "not found, please install libjack0.100.0-dev (or similiar) " + "if you require JACK support") ENDIF(JACK_FOUND) ENDIF(WANT_JACK) @@ -241,7 +248,8 @@ IF(WANT_FFTW3F) SET(LMMS_HAVE_FFTW3F TRUE) SET(STATUS_FFTW3F "OK") ELSE(FFTW3F_FOUND) - SET(STATUS_FFTW3F "not found, please install libfftw3-dev (or similiar)") + SET(STATUS_FFTW3F "not found, libfftw3-dev (or similiar) " + "is highly recommended") ENDIF(FFTW3F_FOUND) ENDIF(WANT_FFTW3F) @@ -253,7 +261,8 @@ IF(WANT_SF2) SET(LMMS_HAVE_FLUIDSYNTH TRUE) SET(STATUS_FLUIDSYNTH "OK") ELSE(FLUIDSYNTH_FOUND) - SET(STATUS_FLUIDSYNTH "not found, please install libfluidsynth-dev (or similiar)") + SET(STATUS_FLUIDSYNTH "not found, libfluidsynth-dev (or similiar)" + "is highly recommended") ENDIF(FLUIDSYNTH_FOUND) ENDIF(WANT_SF2) diff --git a/ChangeLog b/ChangeLog index f6b838acc..7a28f21a5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,39 @@ +2008-09-08 Paul Giblock + + * plugins/sf2_player/sf2_player.cpp: + * plugins/sf2_player/sf2_player.h: + * plugins/midi_import/portsmf: + * plugins/midi_import/portsmf/allegrowr.cpp: + * plugins/midi_import/portsmf/trace.cpp: + * plugins/midi_import/portsmf/algsmfrd_internal.h: + * plugins/midi_import/portsmf/trace.h: + * plugins/midi_import/portsmf/strparse.cpp: + * plugins/midi_import/portsmf/allegrosmfwr.cpp: + * plugins/midi_import/portsmf/mfmidi.cpp: + * plugins/midi_import/portsmf/strparse.h: + * plugins/midi_import/portsmf/mfmidi.h: + * plugins/midi_import/portsmf/allegrord.cpp: + * plugins/midi_import/portsmf/license.txt: + * plugins/midi_import/portsmf/allegrosmfrd.cpp: + * plugins/midi_import/portsmf/allegro.cpp: + * plugins/midi_import/portsmf/allegroserial.cpp: + * plugins/midi_import/portsmf/algrd_internal.h: + * plugins/midi_import/portsmf/allegro.h: + * plugins/midi_import/portsmf/README.txt: + * plugins/midi_import/CMakeLists.txt: + * plugins/midi_import/midi_import.cpp: + * include/meter_model.h: + * include/instrument_track.h: + * include/config_mgr.h: + * include/plugin.h: + * include/setup_dialog.h: + * src/gui/setup_dialog.cpp: + * src/core/plugin.cpp: + * src/core/config_mgr.cpp: + - Add Portsmf-based MIDI import to trunk + - Add default-sf2 to settings dialog, but currently only loads when + importing a MIDI + 2008-09-16 Tobias Doerffel * src/gui/track_container_view.cpp: diff --git a/include/config_mgr.h b/include/config_mgr.h index dcc59df17..b9d2d5abb 100644 --- a/include/config_mgr.h +++ b/include/config_mgr.h @@ -146,6 +146,13 @@ public: } #endif +#ifdef LMMS_HAVE_FLUIDSYNTH + const QString & defaultSoundfont( void ) const + { + return( m_defaultSoundfont ); + } +#endif + inline const QStringList & recentlyOpenedProjects( void ) const { return( m_recentlyOpenedProjects ); @@ -168,6 +175,7 @@ public: void setFLDir( const QString & _fd ); void setLADSPADir( const QString & _fd ); void setSTKDir( const QString & _fd ); + void setDefaultSoundfont( const QString & _sf ); private: @@ -188,6 +196,9 @@ private: QString m_ladDir; #ifdef LMMS_HAVE_STK QString m_stkDir; +#endif +#ifdef LMMS_HAVE_FLUIDSYNTH + QString m_defaultSoundfont; #endif QStringList m_recentlyOpenedProjects; diff --git a/include/instrument_track.h b/include/instrument_track.h index bf1cc25f0..4b2a31e8a 100644 --- a/include/instrument_track.h +++ b/include/instrument_track.h @@ -164,6 +164,20 @@ public: // simple helper for removing midiport-XML-node when loading presets static void removeMidiPortNode( multimediaProject & _mmp ); + floatModel & pitchModel() + { + return m_pitchModel; + } + + floatModel & volumeModel() + { + return m_volumeModel; + } + + floatModel & panningModel() + { + return m_panningModel; + } signals: void instrumentChanged( void ); diff --git a/include/meter_model.h b/include/meter_model.h index f606cfba6..258b72b80 100644 --- a/include/meter_model.h +++ b/include/meter_model.h @@ -44,6 +44,18 @@ public: void reset( void ); + // Must have the sub-models exposed to programatically connect + // to automation or controllers + intModel & numeratorModel( void ) + { + return m_numeratorModel; + } + + intModel & denominatorModel( void ) + { + return m_denominatorModel; + } + private: intModel m_numeratorModel; diff --git a/include/plugin.h b/include/plugin.h index 91c754350..e6a2f4717 100644 --- a/include/plugin.h +++ b/include/plugin.h @@ -42,6 +42,7 @@ class QWidget; class pixmapLoader; class pluginView; +class automatableModel; class EXPORT plugin : public journallingObject, public model @@ -171,10 +172,9 @@ public: // loaded/processed with the help of this plugin virtual void loadFile( const QString & _file ); - // plugins can overload this for making other classes able to query - // settings of the plugin without knowing the actual class - virtual QString getParameter( const QString & _param ); - + // Called if external source needs to change something but we cannot + // reference the class header. Should return null if not key not found. + virtual automatableModel * getChildModel( const QString & _modelName ); // returns an instance of a plugin whose name matches to given one // if specified plugin couldn't be loaded, it creates a dummy-plugin diff --git a/include/setup_dialog.h b/include/setup_dialog.h index 6e9a01aa1..9c548706d 100644 --- a/include/setup_dialog.h +++ b/include/setup_dialog.h @@ -49,7 +49,7 @@ public: enum ConfigTabs { GeneralSettings, - DirectorySettings, + PathSettings, PerformanceSettings, AudioSettings, MidiSettings @@ -69,13 +69,14 @@ private slots: void resetBufSize( void ); void displayBufSizeHelp( void ); - // directory settings widget + // path settings widget void setWorkingDir( const QString & _wd ); void setVSTDir( const QString & _vd ); void setArtworkDir( const QString & _ad ); void setFLDir( const QString & _fd ); void setLADSPADir( const QString & _fd ); void setSTKDir( const QString & _fd ); + void setDefaultSoundfont( const QString & _fd ); // audio settings widget void audioInterfaceChanged( const QString & _driver ); @@ -98,6 +99,7 @@ private slots: void openFLDir( void ); void openLADSPADir( void ); void openSTKDir( void ); + void openDefaultSoundfont( void ); void toggleDisableChActInd( bool _disabled ); void toggleManualChPiano( bool _enabled ); @@ -122,6 +124,9 @@ private: QLineEdit * m_adLineEdit; QLineEdit * m_fdLineEdit; QLineEdit * m_ladLineEdit; +#ifdef LMMS_HAVE_FLUIDSYNTH + QLineEdit * m_sfLineEdit; +#endif #ifdef LMMS_HAVE_STK QLineEdit * m_stkLineEdit; #endif @@ -131,6 +136,9 @@ private: QString m_artworkDir; QString m_flDir; QString m_ladDir; +#ifdef LMMS_HAVE_FLUIDSYNTH + QString m_defaultSoundfont; +#endif #ifdef LMMS_HAVE_STK QString m_stkDir; #endif diff --git a/lmmsconfig.h.in b/lmmsconfig.h.in index ff78543d1..4e0e6861d 100644 --- a/lmmsconfig.h.in +++ b/lmmsconfig.h.in @@ -7,6 +7,7 @@ #cmakedefine LMMS_HAVE_ALSA #cmakedefine LMMS_HAVE_FFTW3F +#cmakedefine LMMS_HAVE_FLUIDSYNTH #cmakedefine LMMS_HAVE_JACK #cmakedefine LMMS_HAVE_OGGVORBIS #cmakedefine LMMS_HAVE_OSS diff --git a/plugins/midi_import/CMakeLists.txt b/plugins/midi_import/CMakeLists.txt index 940b6dd03..dad8e6f3c 100644 --- a/plugins/midi_import/CMakeLists.txt +++ b/plugins/midi_import/CMakeLists.txt @@ -1,3 +1,8 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(midiimport midi_import.cpp midi_import.h) +BUILD_PLUGIN(midiimport midi_import.cpp midi_import.h + portsmf/allegro.cpp portsmf/allegro.h portsmf/allegrosmfwr.cpp + portsmf/allegrord.cpp portsmf/allegrowr.cpp portsmf/allegrosmfrd.cpp + portsmf/mfmidi.cpp portsmf/mfmidi.h portsmf/strparse.cpp + portsmf/strparse.h portsmf/algrd_internal.h portsmf/algsmfrd_internal.h + portsmf/trace.h) diff --git a/plugins/midi_import/midi_import.cpp b/plugins/midi_import/midi_import.cpp index eecb33825..8cdaf9777 100644 --- a/plugins/midi_import/midi_import.cpp +++ b/plugins/midi_import/midi_import.cpp @@ -31,13 +31,17 @@ #include "midi_import.h" #include "track_container.h" #include "instrument_track.h" -#include "pattern.h" +#include "automation_track.h" #include "automation_pattern.h" +#include "config_mgr.h" +#include "pattern.h" #include "instrument.h" +#include "main_window.h" #include "debug.h" #include "embed.h" #include "song.h" +#include "portsmf/allegro.h" #define makeID(_c0, _c1, _c2, _c3) \ ( 0 | \ @@ -109,28 +113,363 @@ bool midiImport::tryImport( trackContainer * _tc ) -static inline unsigned long gcd( unsigned long a, unsigned long b ) +class smfMidiCC { - unsigned long r; - if( a < b ) + +public: + smfMidiCC() : + at( NULL ), + ap( NULL ), + lastPos( 0 ) + { } + + automationTrack * at; + automationPattern * ap; + midiTime lastPos; + + smfMidiCC & create( trackContainer * _tc ) { - r = a; - a = b; - b = r; + if( !at ) + { + at = dynamic_cast( + track::create( track::AutomationTrack, _tc ) ); + } + return *this; } - while( ( r = a % b ) != 0 ) - { - a = b; - b = r; - } - return b; -} + void clear() + { + at = NULL; + ap = NULL; + lastPos = 0; + } + + + smfMidiCC & putValue( midiTime time, automatableModel * objModel, float value ) + { + if( !ap || time > lastPos + DefaultTicksPerTact ) + { + midiTime pPos = midiTime( time.getTact(), 0 ); + ap = dynamic_cast( + at->createTCO(0) ); + ap->movePosition( pPos ); + } + ap->addObject( objModel ); + + lastPos = time; + time = time - ap->startPosition(); + ap->putValue( time, value, false ); + ap->changeLength( midiTime( time.getTact() + 1, 0 ) ); + + return *this; + } +}; + + + +class smfMidiChannel +{ + +public: + smfMidiChannel() : + it( NULL ), + p( NULL ), + it_inst( NULL ), + isSF2( false ), + hasNotes( false ), + lastEnd( 0 ) + { } + + instrumentTrack * it; + pattern * p; + instrument * it_inst; + bool isSF2; + bool hasNotes; + midiTime lastEnd; + + smfMidiChannel * create( trackContainer * _tc ) + { + if( !it ) { + it = dynamic_cast( + track::create( track::InstrumentTrack, _tc ) ); + +#ifdef LMMS_HAVE_FLUIDSYNTH + it_inst = it->loadInstrument( "sf2player" ); + + if( it_inst ) + { + isSF2 = true; + it_inst->loadFile( configManager::inst()->defaultSoundfont() ); + it_inst->getChildModel( "bank" )->setValue( 128 ); + it_inst->getChildModel( "patch" )->setValue( 0 ); + } + else + { + it_inst = it->loadInstrument( "patman" ); + } +#else + it_inst = it->loadInstrument( "patman" ); +#endif + + lastEnd = 0; + } + return this; + } + + + void addNote( note & n ) + { + if( !p || n.pos() > lastEnd + DefaultTicksPerTact ) + { + midiTime pPos = midiTime(n.pos().getTact(), 0 ); + p = dynamic_cast( it->createTCO( 0 ) ); + p->movePosition( pPos ); + } + hasNotes = true; + lastEnd = n.pos() + n.length(); + n.setPos( n.pos( p->startPosition() ) ); + p->addNote( n, false ); + } + +}; bool midiImport::readSMF( trackContainer * _tc ) { + QString filename = file().fileName(); + closeFile(); + + const int preTrackSteps = 2; + QProgressDialog pd( trackContainer::tr( "Importing MIDI-file..." ), + trackContainer::tr( "Cancel" ), 0, preTrackSteps, engine::getMainWindow() ); + pd.setWindowTitle( trackContainer::tr( "Please wait..." ) ); + pd.setWindowModality(Qt::WindowModal); + pd.setMinimumDuration( 0 ); + + pd.setValue( 0 ); + + Alg_seq_ptr seq = new Alg_seq(filename.toLocal8Bit(), true); + seq->convert_to_beats(); + + pd.setMaximum( seq->tracks() + preTrackSteps ); + pd.setValue( 1 ); + + // 128 CC + Pitch Bend + smfMidiCC ccs[129]; + smfMidiChannel chs[256]; + + meterModel & timeSigMM = engine::getSong()->getTimeSigModel(); + automationPattern * timeSigNumeratorPat = + automationPattern::globalAutomationPattern( &timeSigMM.numeratorModel() ); + automationPattern * timeSigDenominatorPat = + automationPattern::globalAutomationPattern( &timeSigMM.denominatorModel() ); + + // TODO: adjust these to Time.Sig changes + double beatsPerTact = 4; + double ticksPerBeat = DefaultTicksPerTact / beatsPerTact; + + // Time-sig changes + Alg_time_sigs * timeSigs = &seq->time_sig; + for( int s = 0; s < timeSigs->length(); ++s ) + { + Alg_time_sig timeSig = (*timeSigs)[s]; + // Initial timeSig, set song-default value + if(/* timeSig.beat == 0*/ true ) + { + // TODO set song-global default value + printf("Another timesig at %f\n", timeSig.beat); + timeSigNumeratorPat->putValue( timeSig.beat*ticksPerBeat, timeSig.num ); + timeSigDenominatorPat->putValue( timeSig.beat*ticksPerBeat, timeSig.den ); + } + else + { + } + + } + + pd.setValue( 2 ); + + // Tempo stuff + automationPattern * tap = _tc->tempoAutomationPattern(); + tap->clear(); + Alg_time_map * timeMap = seq->get_time_map(); + Alg_beats & beats = timeMap->beats; + for( int i = 0; i < beats.len - 1; i++ ) + { + Alg_beat_ptr b = &(beats[i]); + double tempo = ( beats[i + 1].beat - b->beat ) / + ( beats[i + 1].time - beats[i].time ); + tap->putValue( b->beat * ticksPerBeat, tempo * 60.0 ); + } + if( timeMap->last_tempo_flag ) + { + Alg_beat_ptr b = &( beats[beats.len - 1] ); + tap->putValue( b->beat * ticksPerBeat, timeMap->last_tempo * 60.0 ); + } + + // Song events + for( int e = 0; e < seq->length(); ++e ) + { + Alg_event_ptr evt = (*seq)[e]; + + if( evt->is_update() ) + { + printf("Unhandled SONG update: %d %f %s\n", + evt->get_type_code(), evt->time, evt->get_attribute() ); + } + } + + // Tracks + for( int t = 0; t < seq->tracks(); ++t ) + { + Alg_track_ptr trk = seq->track( t ); + pd.setValue( t + preTrackSteps ); + + for( int c = 0; c < 129; c++ ) + { + ccs[c].clear(); + } + + // Now look at events + for( int e = 0; e < trk->length(); ++e ) + { + Alg_event_ptr evt = (*trk)[e]; + + if( evt->chan == -1 ) + { + printf("MISSING GLOBAL THINGY\n"); + printf(" %d %d %f %s\n", evt->chan, + evt->get_type_code(), evt->time, evt->get_attribute() ); + // Global stuff + } + else if( evt->is_note() && evt->chan < 256 ) + { + smfMidiChannel * ch = chs[evt->chan].create( _tc ); + Alg_note_ptr noteEvt = dynamic_cast( evt ); + + note n( noteEvt->get_duration() * ticksPerBeat, + noteEvt->get_start_time() * ticksPerBeat, + noteEvt->get_identifier() - 3, + noteEvt->get_loud()); + ch->addNote( n ); + + } + + else if( evt->is_update() ) + { + smfMidiChannel * ch = chs[evt->chan].create( _tc ); + + double time = evt->time*ticksPerBeat; + QString update( evt->get_attribute() ); + + if( update == "programi" ) + { + long prog = evt->get_integer_value(); + if( ch->isSF2 ) + { + ch->it_inst->getChildModel( "bank" )->setValue( 0 ); + ch->it_inst->getChildModel( "patch" )->setValue( prog ); + } + else { + const QString num = QString::number( prog ); + const QString filter = QString().fill( '0', 3 - num.length() ) + num + "*.pat"; + const QString dir = "/usr/share/midi/" + "freepats/Tone_000/"; + const QStringList files = QDir( dir ). + entryList( QStringList( filter ) ); + if( ch->it_inst && !files.empty() ) + { + ch->it_inst->loadFile( dir+files.front() ); + } + } + } + else if( update == "tracknames" ) + { + QString trackName( evt->get_string_value() ); + ch->it->setName( trackName ); + //ch.p->setName( trackName ); + } + + else if( update.startsWith( "control" ) || update == "bendr" ) + { + int ccid = update.mid( 7, update.length()-8 ).toInt(); + if( update == "bendr" ) + { + ccid = 128; + } + if( ccid <= 128 ) + { + double cc = evt->get_real_value(); + automatableModel * objModel = NULL; + + switch( ccid ) + { + case 0: + if( ch->isSF2 && ch->it_inst ) + { + objModel = ch->it_inst->getChildModel( "bank" ); + printf("BANK SELECT %f %d\n", cc, (int)(cc*127.0)); + cc *= 127.0f; + } + break; + + case 7: + objModel = &ch->it->volumeModel(); + cc *= 100.0f; + break; + + case 10: + objModel = &ch->it->panningModel(); + cc = cc * 200.f - 100.0f; + break; + + case 128: + objModel = &ch->it->pitchModel(); + cc = cc * 100.0f; + break; + } + + if( objModel ) + { + if( time == 0 && objModel ) + { + objModel->setInitValue( cc ); + } + else + { + ccs[ccid].create( _tc ); + ccs[ccid].putValue( time, objModel, cc ); + } + } + } + } + else { + printf("Unhandled update: %d %d %f %s\n", evt->chan, + evt->get_type_code(), evt->time, evt->get_attribute() ); + } + } + } + } + + delete seq; + + + for( int c=0; c < 256; ++c ) + { + if( !chs[c].hasNotes && chs[c].it ) + { + printf(" Should remove empty track\n"); + // must delete trackView first - but where is it? + //_tc->removeTrack( chs[c].it ); + //it->deleteLater(); + } + } + + return true; + + + /* // the curren position is immediately after the "MThd" id int header_len = readInt( 4 ); if( header_len < 6 ) @@ -156,6 +495,7 @@ invalid_format: num_tracks = 0; return( FALSE ); } + #ifdef LMMS_DEBUG printf( "tracks: %d\n", num_tracks ); #endif @@ -166,193 +506,12 @@ invalid_format: goto invalid_format; } #ifdef LMMS_DEBUG - printf( "time-division: %d\n", m_timingDivision ); + printf( "time-division: %d\n", m_timingDivision ); #endif - QProgressDialog pd( trackContainer::tr( "Importing MIDI-file..." ), - trackContainer::tr( "Cancel" ), 0, num_tracks ); - pd.setWindowTitle( trackContainer::tr( "Please wait..." ) ); - pd.show(); - // calculate some timing stuff - int crotchet_time = 16*3; - int divisor = m_timingDivision ? m_timingDivision : 96; - int multiplier = crotchet_time; - int g = gcd( crotchet_time, divisor ); - multiplier /= g; - divisor /= g; + */ - // try to set default tempo - automationPattern * tap = _tc->tempoAutomationPattern(); - tap->clear(); - tap->putValue( 0, 120 ); - - // read tracks - for( int i = 0; i < num_tracks; ++i ) - { - pd.setValue( i ); - qApp->processEvents( QEventLoop::AllEvents, 100 ); - - if( pd.wasCanceled() ) - { - return( FALSE ); - } - - int len; - - // search for MTrk chunk - while( 1 ) - { - Sint32 id = readID(); - len = readInt( 4 ); - if( file().atEnd() ) - { - printf( "midiImport::readSMF(): unexpected end " - "of file\n" ); - return( FALSE ); - } - if( len < 0 || len >= 0x10000000 ) - { - printf( "midiImport::readSMF(): invalid chunk " - "length %d\n", len ); - return( FALSE ); - } - if( id == makeID( 'M', 'T', 'r', 'k' ) ) - { - break; - } - skip( len ); - } - if( len <= 0 ) - { - continue; - } - - QString track_name = ""; - - if( !readTrack( file().pos() + len, track_name ) ) - { - return( FALSE ); - } - if( i == 0 ) - { - if( tap == NULL ) - { - continue; - } - for( eventVector::const_iterator it = m_events.begin(); - it != m_events.end(); ++it ) - { - const int tick = it->first; - const midiEvent & ev = it->second; - if( ev.m_type == MidiMetaEvent ) - { - switch( ev.m_data.m_param[0] ) - { - case MidiSetTempo: - { - tap->putValue( midiTime( ( tick * multiplier ) / divisor ), - ev.m_data.m_param[1], FALSE ); - break; - } - default: - break; - } - } - } - continue; - } - - // now create new instrument-track for reading in track - instrumentTrack * it = dynamic_cast( - track::create( - track::InstrumentTrack, - _tc ) ); -#ifdef LMMS_DEBUG - assert( it != NULL ); -#endif - // TODO: setup program, channel etc. - instrument * it_inst = it->loadInstrument( "patman" ); - bool sample_loaded = FALSE; - - // TODO: track_name.trimmed().isEmpty() (Qt4) - if( !track_name.isEmpty() ) - { - it->setName( track_name ); - } - - // now create pattern to store notes in - pattern * p = dynamic_cast( it->createTCO( 0 ) ); -#ifdef LMMS_DEBUG - assert( p != NULL ); -#endif - it->addTCO( p ); - - // init keys - int keys[NumKeys][2]; - for( int j = 0; j < NumKeys; ++j ) - { - keys[j][0] = -1; - } - - // now process every event - for( eventVector::const_iterator it = m_events.begin(); - it != m_events.end(); ++it ) - { - const int tick = it->first; - const midiEvent & ev = it->second; - switch( ev.m_type ) - { - case MidiNoteOn: - if( ev.key() >= NumKeys ) - { - continue; - } - if( ev.velocity() > 0 ) - { - keys[ev.key()][0] = tick; - keys[ev.key()][1] = - ev.velocity(); - break; - } - - case MidiNoteOff: - if( ev.key() < NumKeys && - keys[ev.key()][0] >= 0 ) - { - note n( midiTime( ( ( tick - keys[ev.key()][0] ) * multiplier ) / divisor ), - midiTime( ( keys[ev.key()][0] * multiplier ) / divisor ), - ev.key(), keys[ev.key()][1] * 100 / 128 ); - p->addNote( n, FALSE ); - keys[ev.key()][0] = -1; - } - break; - - case MidiProgramChange: - { - const QString num = QString::number( ev.key() ); - const QString filter = QString().fill( '0', 3 - num.length() ) + num + "*.pat"; - const QString dir = "/usr/share/midi/" - "freepats/Tone_000/"; - const QStringList files = QDir( dir ). - entryList( QStringList( - filter ) ); - if( it_inst && !files.empty() && !sample_loaded ) - { - it_inst->loadFile( dir+files.front() ); - sample_loaded = TRUE; - } - break; - } - - default: - /* printf( "Unhandled event: %#x\n", - (int) ev.m_type );*/ - break; - } - } - } - return( TRUE ); } @@ -360,252 +519,44 @@ invalid_format: bool midiImport::readRIFF( trackContainer * _tc ) { - // skip file length + // skip file length skip( 4 ); - // check file type ("RMID" = RIFF MIDI) - if( readID() != makeID( 'R', 'M', 'I', 'D' ) ) + // check file type ("RMID" = RIFF MIDI) + if( readID() != makeID( 'R', 'M', 'I', 'D' ) ) { invalid_format: - printf( "midiImport::readRIFF(): invalid file format\n" ); - return( FALSE ); - } - // search for "data" chunk - while( 1 ) + printf( "midiImport::readRIFF(): invalid file format\n" ); + return( FALSE ); + } + // search for "data" chunk + while( 1 ) { - int id = readID(); - int len = read32LE(); - if( file().atEnd() ) + int id = readID(); + int len = read32LE(); + if( file().atEnd() ) { data_not_found: - printf( "midiImport::readRIFF(): data chunk not " + printf( "midiImport::readRIFF(): data chunk not " "found\n" ); - return( FALSE ); - } - if( id == makeID( 'd', 'a', 't', 'a' ) ) - { - break; - } - if( len < 0 ) - { - goto data_not_found; - } - skip( ( len + 1 ) & ~1 ); - } - // the "data" chunk must contain data in SMF format - if( readID() != makeID( 'M', 'T', 'h', 'd' ) ) - { - goto invalid_format; - } - return( readSMF( _tc ) ); -} - - - - -bool midiImport::readTrack( int _track_end, QString & _track_name ) -{ - int tick = 0; - unsigned char last_cmd = 0; -// unsigned char port = 0; - - m_events.clear(); - // the current file position is after the track ID and length - while( (int) file().pos() < _track_end ) - { - unsigned char cmd; - int len; - - int delta_ticks = readVar(); - if( delta_ticks < 0 ) - { - break; - } - tick += delta_ticks; - - int c = readByte(); - if( c < 0 ) - { - break; - } - if( c & 0x80 ) - { - // have command - cmd = c; - if( cmd < 0xf0 ) - { - last_cmd = cmd; - } - } - else - { - // running status - ungetChar( c ); - cmd = last_cmd; - if( !cmd ) - { - error(); - return( FALSE ); - } - } - switch( cmd & 0xF0 ) - { - // channel msg with 2 parameter bytes - case MidiNoteOff: - case MidiNoteOn: - case MidiKeyPressure: - case MidiControlChange: - case MidiPitchBend: - { - int data1 = readByte() & 0x7F; - int data2 = readByte() & 0x7F; - m_events.push_back( qMakePair( tick, - midiEvent( static_cast( - cmd & 0xF0 ), - cmd & 0x0F, - data1, - data2 ) ) ); - break; - } - // channel msg with 1 parameter byte - case MidiProgramChange: - case MidiChannelPressure: - m_events.push_back( qMakePair( tick, - midiEvent( static_cast( - cmd & 0xF0 ), - cmd & 0x0F, - readByte() & 0x7F ) ) ); - break; - - case MidiSysEx: - switch( cmd ) - { - case MidiSysEx: - case MidiEOX: - { - len = readVar(); - if( len < 0 ) - { - error(); - return( FALSE ); - } - if( cmd == MidiSysEx ) - { - ++len; - } - char * data = new char[len]; - if( cmd == MidiSysEx ) - { - data[0] = MidiSysEx; - } - for( ; c < len; ++c ) - { - data[c] = readByte(); - } - m_events.push_back( - qMakePair( tick, - midiEvent( MidiSysEx, data, len ) ) ); - break; - } - - case MidiMetaEvent: - c = readByte(); - len = readVar(); -/* if( len < 0 ) - { - error(); - return( FALSE ); - }*/ - switch( c ) - { - case MidiTrackName: - if( len > 0 ) - { - char * n = new char[len+1]; - readBlock( n, len ); - n[len] = 0; - _track_name += n; - delete[] n; - } - break; - case MidiPortNumber: - if( len < 1 ) - { - error(); - return( FALSE ); - } -/* port = readByte() % - port_count; - skip( len - 1 );*/ - skip( len ); - break; - - case MidiEOT: - //track->end_tick = tick; - skip( _track_end - - file().pos() ); - return( TRUE ); - - case MidiSetTempo: // tempo - { - if( len < 3 ) - { - error(); - return( FALSE ); - } - int tempo = readByte() << 16; - tempo |= readByte() << 8; - tempo |= readByte(); - tempo = ( 60*1000*1000 ) / tempo; - m_events.push_back( qMakePair( tick, midiEvent( MidiMetaEvent, 0, MidiSetTempo, tempo ) ) ); - break; - } - case MidiTimeSignature: - { - int nominator = readByte(); - int denominator = 1 << (int) readByte(); - // clocks - readByte(); - // notes - readByte(); - if( nominator == 0 ) - { - nominator = 4; - } - if( denominator == 0 ) - { - denominator = 4; - } - engine::getSong()->getTimeSigModel().setNumerator( nominator ); - engine::getSong()->getTimeSigModel().setDenominator( denominator ); -#ifdef LMMS_DEBUG - printf("nom:%d denom:%d\n",nominator,denominator); -#endif - break; - } - default:// ignore all other - // meta events -#ifdef LMMS_DEBUG - printf("meta event %d\n", (int) c ); -#endif - skip( len ); - break; - } - break; - - default: // invalid Fx command - error(); return( FALSE ); } - break; - - default: // cannot happen - error(); - return( FALSE ); - } - } - error(); - return( FALSE ); + if( id == makeID( 'd', 'a', 't', 'a' ) ) + { + break; + } + if( len < 0 ) + { + goto data_not_found; + } + skip( ( len + 1 ) & ~1 ); + } + // the "data" chunk must contain data in SMF format + if( readID() != makeID( 'M', 'T', 'h', 'd' ) ) + { + goto invalid_format; + } + return( readSMF( _tc ) ); } diff --git a/plugins/midi_import/portsmf/README.txt b/plugins/midi_import/portsmf/README.txt new file mode 100644 index 000000000..0f36c9e4e --- /dev/null +++ b/plugins/midi_import/portsmf/README.txt @@ -0,0 +1,32 @@ +portsmf README.txt +14 Jun 2008 +Roger B. Dannenberg + +Portsmf is "Port Standard MIDI File", a cross-platform, C++ library +for reading and writing Standard MIDI Files. + +License information: free and open source, see license.txt for details + +Features: + +- input and output of Standard MIDI Files +- data structures, classes, etc. for representing music data in memory + o sequence structure consisting of multiple tracks + o track structure consisting of multiple events + o events contain note and control data + o extensible attribute-value property lists + o tempo track and time signature representation +- input and output of a text-based representation: Allegro files +- extensive editing operations on sequences and tracks +- conversion to/from binary buffers for archiving, undo/redo, etc. + +Portsmf is a relatively small number of about 9 files, so there is +currently no support for building/maintaining Portsmf as a separate +library. (Contributions are welcome.) For now, it is suggested that +you simply compile these files along with your application sources. + +There is a test program in portsmf_test and makefiles to build it as +an example. + +You might want to browse through portsmf_test/allegro_test.cpp +for examples that use and exercise most of the portsmf functions. diff --git a/plugins/midi_import/portsmf/algrd_internal.h b/plugins/midi_import/portsmf/algrd_internal.h new file mode 100644 index 000000000..3b77adc4c --- /dev/null +++ b/plugins/midi_import/portsmf/algrd_internal.h @@ -0,0 +1,4 @@ +/* algread_internal.h -- interface between allegro.cpp and allegrord.cpp */ + +Alg_error alg_read(std::istream &file, Alg_seq_ptr new_seq); + diff --git a/plugins/midi_import/portsmf/algsmfrd_internal.h b/plugins/midi_import/portsmf/algsmfrd_internal.h new file mode 100644 index 000000000..75cc0093b --- /dev/null +++ b/plugins/midi_import/portsmf/algsmfrd_internal.h @@ -0,0 +1,3 @@ +/* algsmfrd_internal.h -- interface from allegrosmfrd.cpp to allegro.cpp */ + +Alg_error alg_smf_read(std::istream &file, Alg_seq_ptr new_seq); diff --git a/plugins/midi_import/portsmf/allegro.cpp b/plugins/midi_import/portsmf/allegro.cpp new file mode 100644 index 000000000..2c26a3e94 --- /dev/null +++ b/plugins/midi_import/portsmf/allegro.cpp @@ -0,0 +1,2869 @@ +// Allegro: music representation system, with +// extensible in-memory sequence structure +// upward compatible with MIDI +// implementations in C++ and Serpent +// external, text-based representation +// compatible with Aura +// +/* CHANGE LOG: +04 apr 03 -- fixed bug in add_track that caused infinite loop +*/ + +#include "assert.h" +#include "stdlib.h" +#include "stdio.h" +#include "string.h" +#include "memory.h" +#include +#include +using namespace std; +#include "allegro.h" +#include "algrd_internal.h" +#include "algsmfrd_internal.h" +// #include "trace.h" -- only needed for debugging +#include "math.h" + +#define STREQL(x, y) (strcmp(x, y) == 0) +#define MAX(x, y) ((x) > (y) ? (x) : (y)) + +// 4311 is type cast ponter to long warning +// 4996 is warning against strcpy +// 4267 is size_t to long warning +#pragma warning(disable: 4311 4996 4267) +Alg_atoms symbol_table; +Serial_buffer Alg_track::ser_buf; // declare the static variable + +bool within(double d1, double d2, double epsilon) +{ + d1 -= d2; + return d1 < epsilon && d1 > -epsilon; +} + + +char *heapify(const char *s) +{ + char *h = new char[strlen(s) + 1]; + strcpy(h, s); + return h; +} + + +void Alg_atoms::expand() +{ + maxlen = (maxlen + 5); // extra growth for small sizes + maxlen += (maxlen >> 2); // add 25% + char **new_atoms = new Alg_attribute[maxlen]; + // now do copy + memcpy(new_atoms, atoms, len * sizeof(Alg_attribute)); + if (atoms) delete[] atoms; + atoms = new_atoms; +} + + +// insert_new -- insert an attribute name and type +// +// attributes are stored as a string consisting of the type +// (a char) followed by the attribute name. This makes it +// easy to retrieve the type or the name or both. +// +Alg_attribute Alg_atoms::insert_new(const char *name, char attr_type) +{ + if (len == maxlen) expand(); + char *h = new char[strlen(name) + 2]; + strcpy(h + 1, name); + *h = attr_type; + atoms[len++] = h; + return h; +} + + +Alg_attribute Alg_atoms::insert_attribute(Alg_attribute attr) +{ + for (int i = 0; i < len; i++) { + if (STREQL(attr, atoms[i])) { + return atoms[i]; + } + } + return insert_new(attr + 1, attr[0]); +} + + +Alg_attribute Alg_atoms::insert_string(const char *name) +{ + char attr_type = name[strlen(name) - 1]; + for (int i = 0; i < len; i++) { + if (attr_type == atoms[i][0] && + STREQL(name, atoms[i] + 1)) { + return atoms[i]; + } + } + return insert_new(name, attr_type); +} + + +void Alg_parameter::copy(Alg_parameter_ptr parm) +{ + *this = *parm; // copy all fields + // if the value is a string, copy the string + if (attr_type() == 's') { + s = heapify(s); + } +} + + +void Alg_parameter::show() +{ + switch (attr[0]) { + case 'r': + printf("%s:%g", attr_name(), r); + break; + case 's': + printf("%s:%s", attr_name(), s); + break; + case 'i': + printf("%s:%d", attr_name(), i); + break; + case 'l': + printf("%s:%s", attr_name(), (l ? "t" : "f")); + break; + case 'a': + printf("%s:%s", attr_name(), a); + break; + } +} + + +Alg_parameter::~Alg_parameter() +{ + if (attr_type() == 's' && s) { + delete[] s; + } +} + + +void Alg_parameters::insert_real(Alg_parameters **list, char *name, double r) +{ + Alg_parameters_ptr a = new Alg_parameters(*list); + *list = a; + a->parm.set_attr(symbol_table.insert_string(name)); + a->parm.r = r; + assert(a->parm.attr_type() == 'r'); +} + + +void Alg_parameters::insert_string(Alg_parameters **list, char *name, char *s) +{ + Alg_parameters_ptr a = new Alg_parameters(*list); + *list = a; + a->parm.set_attr(symbol_table.insert_string(name)); + // string is deleted when parameter is deleted + a->parm.s = heapify(s); + assert(a->parm.attr_type() == 's'); +} + + +void Alg_parameters::insert_integer(Alg_parameters **list, char *name, long i) +{ + Alg_parameters_ptr a = new Alg_parameters(*list); + *list = a; + a->parm.set_attr(symbol_table.insert_string(name)); + a->parm.i = i; + assert(a->parm.attr_type() == 'i'); +} + + +void Alg_parameters::insert_logical(Alg_parameters **list, char *name, bool l) +{ + Alg_parameters_ptr a = new Alg_parameters(*list); + *list = a; + a->parm.set_attr(symbol_table.insert_string(name)); + a->parm.l = l; + assert(a->parm.attr_type() == 'l'); +} + + +void Alg_parameters::insert_atom(Alg_parameters **list, char *name, char *s) +{ + Alg_parameters_ptr a = new Alg_parameters(*list); + *list = a; + a->parm.set_attr(symbol_table.insert_string(name)); + a->parm.a = symbol_table.insert_string(s); + assert(a->parm.attr_type() == 'a'); +} + + +Alg_parameters *Alg_parameters::remove_key(Alg_parameters **list, char *name) +{ + while (*list) { + if (STREQL((*list)->parm.attr_name(), name)) { + Alg_parameters_ptr p = *list; + *list = p->next; + p->next = NULL; + return p; // caller should free this pointer + } + list = &((*list)->next); + } + return NULL; +} + + +Alg_parameter_ptr Alg_parameters::find(Alg_attribute *attr) +{ + assert(attr); + Alg_parameters_ptr temp = this; + while (temp) { + if (temp->parm.attr == *attr) { + return &(temp->parm); + } + } + return NULL; +} + + +int Alg_event::get_type_code() +{ + if (!is_note()) { + const char* attr = get_attribute(); + if (STREQL(attr, "gate")) // volume change + return ALG_GATE; + if (STREQL(attr, "bend")) // pitch bend + return ALG_BEND; + if (strncmp(attr, "control", 7) == 0) // control change + // note that midi control changes have attributes of the form + // "control" where n is the decimal number (as a character string) + // of the midi controller, e.g. control2 is the breath controller. + // We don't check for decimal numbers in the range 0-127, so any + // attribute that begins with "control" is an ALG_CONTROL: + return ALG_CONTROL; + if (STREQL(attr, "program")) // program change + return ALG_PROGRAM; + if (STREQL(attr, "pressure")) // pressure change + return ALG_PRESSURE; + if (STREQL(attr, "keysig")) // key signature + return ALG_KEYSIG; + if (STREQL(attr, "timesig_num")) // time signature numerator + return ALG_TIMESIG_NUM; + if (STREQL(attr, "timesig_den")) // time signature denominator + return ALG_TIMESIG_DEN; + return ALG_OTHER; + } + return ALG_NOTE; // it is a note +} + + +void Alg_event::set_parameter(Alg_parameter_ptr new_parameter) +{ + Alg_parameter_ptr parm; + if (is_note()) { + Alg_note_ptr note = (Alg_note_ptr) this; + parm = note->parameters->find(&(new_parameter->attr)); + if (!parm) { + note->parameters = new Alg_parameters(note->parameters); + parm = &(note->parameters->parm); + } + } else { // update + Alg_update_ptr update = (Alg_update_ptr) this; + parm = &(update->parameter); + } + parm->copy(new_parameter); // copy entire parameter +} + + +void Alg_event::set_string_value(char *a, char *value) +{ + assert(a); // must be non-null + Alg_attribute attr = symbol_table.insert_string(a); + assert(attr[0] == 's'); + Alg_parameter parm; + parm.set_attr(attr); + parm.s = value; + set_parameter(&parm); + parm.s = NULL; // do this to prevent string from being freed +} + + +void Alg_event::set_real_value(char *a, double value) +{ + assert(a); // must be non-null + // attr is like a, but it has the type code prefixed for + // fast lookup, and it is a unique string in symbol_table + // e.g. a="attackr" -> attr="rattackr" + Alg_attribute attr = symbol_table.insert_string(a); + assert(attr[0] == 'r'); + Alg_parameter parm; + parm.set_attr(attr); + parm.r = value; + set_parameter(&parm); + // since type is 'r' we don't have to NULL the string +} + + +void Alg_event::set_logical_value(char *a, bool value) +{ + assert(a); // must be non-null + Alg_attribute attr = symbol_table.insert_string(a); + assert(attr[0] == 'l'); + Alg_parameter parm; + parm.set_attr(attr); + parm.l = value; + set_parameter(&parm); + // since type is 'l' we don't have to NULL the string +} + + +void Alg_event::set_integer_value(char *a, long value) +{ + assert(a); // must be non-null + Alg_attribute attr = symbol_table.insert_string(a); + assert(attr[0] == 'i'); + Alg_parameter parm; + parm.set_attr(attr); + parm.i = value; + set_parameter(&parm); + // since tpye is 'i' we don't have to NULL the string +} + + +void Alg_event::set_atom_value(char *a, char *value) +{ + assert(a); // must be non-null + Alg_attribute attr = symbol_table.insert_string(a); + assert(attr[0] == 'a'); + Alg_parameter parm; + parm.set_attr(attr); + parm.a = value; + set_parameter(&parm); + /* since type is 'a' we don't have to null the string */ +} + + +float Alg_event::get_pitch() +{ + assert(is_note()); + Alg_note* note = (Alg_note *) this; + return note->pitch; +} + + +float Alg_event::get_loud() +{ + assert(is_note()); + Alg_note* note = (Alg_note *) this; + return note->loud; +} + + +double Alg_event::get_start_time() +{ + assert(is_note()); + Alg_note* note = (Alg_note *) this; + return note->time; +} + + +double Alg_event::get_end_time() +{ + assert(is_note()); + Alg_note* note = (Alg_note *) this; + return note->time + note->dur; +} + + +double Alg_event::get_duration() +{ + assert(is_note()); + Alg_note* note = (Alg_note *) this; + return note->dur; +} + + +void Alg_event::set_pitch(float p) +{ + assert(is_note()); + Alg_note* note = (Alg_note *) this; + note->pitch = p; +} + +void Alg_event::set_loud(float l) +{ + assert(is_note()); + Alg_note *note = (Alg_note *) this; + note->loud = l; +} + + +void Alg_event::set_duration(double d) +{ + assert(is_note()); + Alg_note* note = (Alg_note *) this; + note->dur = d; +} + + +bool Alg_event::has_attribute(char *a) +{ + assert(is_note()); + assert(a); // must be non-null + Alg_note* note = (Alg_note *) this; + Alg_attribute attr = symbol_table.insert_string(a); + Alg_parameter_ptr parm = note->parameters->find(&attr); + return parm != NULL; +} + + +char Alg_event::get_attribute_type(char *a) +{ + assert(is_note()); + assert(a); + return a[strlen(a) - 1]; +} + + +char *Alg_event::get_string_value(char *a, char *value) +{ + assert(is_note()); + assert(a); // must be non-null + Alg_note* note = (Alg_note *) this; + Alg_attribute attr = symbol_table.insert_string(a); + assert(a[0] == 's'); // must be of type string + Alg_parameter_ptr parm = note->parameters->find(&attr); + if (parm) return parm->s; + return value; +} + + +double Alg_event::get_real_value(char *a, double value) +{ + assert(is_note()); + assert(a); + Alg_note* note = (Alg_note *) this; + Alg_attribute attr = symbol_table.insert_string(a); + assert(a[0] == 'r'); // must be of type real + Alg_parameter_ptr parm = note->parameters->find(&attr); + if (parm) return parm->r; + return value; +} + + +bool Alg_event::get_logical_value(char *a, bool value) +{ + assert(is_note()); + assert(a); + Alg_note* note = (Alg_note *) this; + Alg_attribute attr = symbol_table.insert_string(a); + assert(a[0] == 'l'); // must be of type logical + Alg_parameter_ptr parm = note->parameters->find(&attr); + if (parm) return parm->l; + return value; +} + + +long Alg_event::get_integer_value(char *a, long value) +{ + assert(is_note()); + assert(a); + Alg_note* note = (Alg_note *) this; + Alg_attribute attr = symbol_table.insert_string(a); + assert(a[0] == 'i'); // must be of type integer + Alg_parameter_ptr parm = note->parameters->find(&attr); + if (parm) return parm->i; + return value; +} + + +char *Alg_event::get_atom_value(char *a, char *value) +{ + assert(is_note()); + assert(a); + Alg_note* note = (Alg_note *) this; + Alg_attribute attr = symbol_table.insert_string(a); + assert(a[0] == 'a'); // must be of type atom + Alg_parameter_ptr parm = note->parameters->find(&attr); + if (parm) return parm->a; + // if default is a string, convert to an atom (unique + // string in symbol table) and return it + return (value == NULL ? NULL : + symbol_table.insert_string(value)); +} + + +void Alg_event::delete_attribute(char *a) +{ + assert(is_note()); + Alg_note* note = (Alg_note *) this; + Alg_parameters::remove_key(&(note->parameters), a); +} + + +const char *Alg_event::get_attribute() +// Note: this returns a string, not an Alg_attribute +{ + assert(is_update()); + Alg_update* update = (Alg_update *) this; + return update->parameter.attr_name(); +} + + +char Alg_event::get_update_type() +{ + assert(is_update()); + Alg_update* update = (Alg_update *) this; + return update->parameter.attr_type(); +} + + +char *Alg_event::get_string_value() +{ + assert(is_update()); + Alg_update* update = (Alg_update *) this; + assert(get_update_type() == 's'); + return update->parameter.attr_name(); +} + + +double Alg_event::get_real_value() +{ + assert(is_update()); + Alg_update* update = (Alg_update *) this; + assert(get_update_type() == 'r'); + return update->parameter.r; +} + + +bool Alg_event::get_logical_value() +{ + assert(is_update()); + Alg_update* update = (Alg_update *) this; + assert(get_update_type() == 'l'); + return update->parameter.l; +} + + +long Alg_event::get_integer_value() +{ + assert(is_update()); + Alg_update* update = (Alg_update *) this; + assert(get_update_type() == 'i'); + return update->parameter.i; +} + + +char *Alg_event::get_atom_value() +{ + assert(is_update()); + Alg_update* update = (Alg_update *) this; + assert(get_update_type() == 'a'); + return update->parameter.a; +} + + +bool Alg_event::overlap(double t, double len, bool all) +{ + // event starts within region + if (time >= t && time <= t + len - ALG_EPS) + return true; + if (all && is_note()) { + double dur = ((Alg_note_ptr) this)->dur; + // note ends within region + if (time < t && time + dur - ALG_EPS > t) + return true; + } + // does not overlap + return false; +} + + +Alg_note::Alg_note(Alg_note_ptr note) +{ + *this = *note; // copy all fields + // parameters is now a shared pointer. We need to copy the + // parameters + Alg_parameters_ptr next_param_ptr = parameters; + while (next_param_ptr) { + Alg_parameters_ptr new_params = new Alg_parameters(next_param_ptr->next); + new_params->parm.copy(&(next_param_ptr->parm)); // copy the attribute and value + next_param_ptr = new_params->next; + } +} + + +Alg_note::~Alg_note() +{ + while (parameters) { + Alg_parameters_ptr to_delete = parameters; + parameters = parameters->next; + delete to_delete; + } +} + + +void Alg_note::show() +{ + printf("Alg_note: time %g, chan %d, dur %g, key %d, " + "pitch %g, loud %g, attributes ", + time, chan, dur, key, pitch, loud); + Alg_parameters_ptr parms = parameters; + while (parms) { + parms->parm.show(); + printf(" "); + parms = parms->next; + } + printf("\n"); +} + + +Alg_update::Alg_update(Alg_update_ptr update) +{ + *this = *update; // copy all fields + // parameter requires careful copy to possibly duplicate string value: + this->parameter.copy(&(update->parameter)); +} + + +void Alg_update::show() +{ + printf("Alg_update: "); + parameter.show(); + printf("\n"); +} + + +void Alg_events::expand() +{ + maxlen = (maxlen + 5); // extra growth for small sizes + maxlen += (maxlen >> 2); // add 25% + Alg_event_ptr *new_events = new Alg_event_ptr[maxlen]; + // now do copy + memcpy(new_events, events, len * sizeof(Alg_event_ptr)); + if (events) delete[] events; + events = new_events; +} + + +void Alg_events::insert(Alg_event_ptr event) +{ + if (maxlen <= len) { + expand(); + } + // Note: if the new event is the last one, the assignment + // events[i] = event; (below) will never execute, so just + // in case, we do the assignment here. events[len] will + // be replaced during the memmove() operation below if + // this is not the last event. + events[len] = event; + len++; + // find insertion point: (this could be a binary search) + for (int i = 0; i < len; i++) { + if (events[i]->time > event->time) { + // insert event at i + memmove(&events[i + 1], &events[i], + sizeof(Alg_event_ptr) * (len - i - 1)); + events[i] = event; + return; + } + } +} + +Alg_event_ptr Alg_events::uninsert(long index) +{ + assert(0 <= index && index < len); + Alg_event_ptr event = events[index]; + printf("memmove: %x from %x (%d)\n", events + index, events + index + 1, + sizeof(Alg_event_ptr) * (len - index - 1)); + memmove(events + index, events + index + 1, + sizeof(Alg_event_ptr) * (len - index - 1)); + len--; + return event; +} + + +void Alg_events::append(Alg_event_ptr event) +{ + if (maxlen <= len) { + expand(); + } + events[len++] = event; + // keep track of last note_off time + if (event->is_note()) { + Alg_note_ptr note = (Alg_note_ptr) event; + double note_off = note->time + note->dur; + if (note_off > last_note_off) + last_note_off = note_off; + } +} + + +Alg_events::~Alg_events() +{ + // individual events are not deleted, only the array + if (events) { + delete[] events; + } +} + + +Alg_event_list::Alg_event_list(Alg_track *owner) +{ + events_owner = owner; + sequence_number = owner->sequence_number; + beat_dur = 0.0; real_dur = 0.0; type = 'e'; +} + + +Alg_event_ptr &Alg_event_list::operator [](int i) +{ + assert(i >= 0 && i < len); + return events[i]; +} + + +Alg_event_list::~Alg_event_list() +{ + // note that the events contained in the list are not destroyed +} + + +void Alg_event_list::set_start_time(Alg_event *event, double t) +{ + // For Alg_event_list, find the owner and do the update there + // For Alg_track, change the time and move the event to the right place + // For Alg_seq, find the track and do the update there + + long index, i; + Alg_track_ptr track_ptr; + if (type == 'e') { // this is an Alg_event_list + // make sure the owner has not changed its event set + assert(events_owner && + sequence_number == events_owner->sequence_number); + // do the update on the owner + events_owner->set_start_time(event, t); + return; + } else if (type == 't') { // this is an Alg_track + // find the event in the track + track_ptr = (Alg_track_ptr) this; + // this should be a binary search since events are in time order + // probably there should be member function to do the search + for (index = 0; index < length(); index++) { + if ((*track_ptr)[index] == event) goto found_event; + } + } else { // type == 's', an Alg_seq + Alg_seq_ptr seq = (Alg_seq_ptr) this; + for (i = 0; i < seq->tracks(); i++) { + track_ptr = seq->track(i); + // if you implemented binary search, you could call it + // instead of this loop too. + for (index = 0; index < track_ptr->length(); index++) { + if ((*track_ptr)[index] == event) goto found_event; + } + } + } + assert(false); // event not found seq or track! + found_event: + // at this point, track[index] == event + // we could be clever and figure out exactly what notes to move + // but it is simpler to just remove the event and reinsert it: + track_ptr->uninsert(index); + event->time = t; + track_ptr->insert(event); +} + + +void Alg_beats::expand() +{ + maxlen = (maxlen + 5); // extra growth for small sizes + maxlen += (maxlen >> 2); // add 25% + Alg_beat_ptr new_beats = new Alg_beat[maxlen]; + // now do copy + memcpy(new_beats, beats, len * sizeof(Alg_beat)); + if (beats) delete[] beats; + beats = new_beats; +} + + +void Alg_beats::insert(long i, Alg_beat_ptr beat) +{ + assert(i >= 0 && i <= len); + if (maxlen <= len) { + expand(); + } + memmove(&beats[i + 1], &beats[i], sizeof(Alg_beat) * (len - i)); + memcpy(&beats[i], beat, sizeof(Alg_beat)); + len++; +} + + +Alg_time_map::Alg_time_map(Alg_time_map *map) +{ + refcount = 0; + assert(map->beats[0].beat == 0 && map->beats[0].time == 0); + assert(map->beats.len > 0); + // new_beats[0] = map->beats[0]; + // this is commented because + // both new_beats[0] and map->beats[0] should be (0, 0) + for (int i = 1; i < map->beats.len; i++) { + beats.insert(i, &map->beats[i]); + } + last_tempo = map->last_tempo; + last_tempo_flag = map->last_tempo_flag; +} + + +void Alg_time_map::show() +{ + printf("Alg_time_map: "); + for (int i = 0; i < beats.len; i++) { + Alg_beat &b = beats[i]; + printf("(%g, %g) ", b.time, b.beat); + } + printf("last tempo: %g\n", last_tempo); +} + + +long Alg_time_map::locate_time(double time) +{ + int i = 0; + while ((i < beats.len) && (time > beats[i].time)) { + i++; + } + return i; +} + + +long Alg_time_map::locate_beat(double beat) +{ + int i = 0; + while ((i < beats.len) && (beat > beats[i].beat)) { + i++; + } + return i; +} + + +double Alg_time_map::beat_to_time(double beat) +{ + Alg_beat_ptr mbi; + Alg_beat_ptr mbi1; + if (beat <= 0) { + return beat; + } + int i = locate_beat(beat); + if (i == beats.len) { + if (last_tempo_flag) { + return beats[i - 1].time + + (beat - beats[i - 1].beat) / last_tempo; + } else if (i == 1) { + return beat * 60.0 / ALG_DEFAULT_BPM; + // so we use that as default allegro tempo too + } else { + mbi = &beats[i - 2]; + mbi1 = &beats[i - 1]; + } + } else { + mbi = &beats[i - 1]; + mbi1 = &beats[i]; + } + // whether w extrapolate or interpolate, the math is the same + double time_dif = mbi1->time - mbi->time; + double beat_dif = mbi1->beat - mbi->beat; + return mbi->time + (beat - mbi->beat) * time_dif / beat_dif; +} + + +double Alg_time_map::time_to_beat(double time) +{ + Alg_beat_ptr mbi; + Alg_beat_ptr mbi1; + if (time <= 0.0) return time; + int i = locate_time(time); + if (i == beats.len) { + if (last_tempo_flag) { + return beats[i - 1].beat + + (time - beats[i - 1].time) * last_tempo; + } else if (i == 1) { + return time * (ALG_DEFAULT_BPM / 60.0); + } else { + mbi = &beats[i - 2]; + mbi1 = &beats[i - 1]; + } + } else { + mbi = &beats[i - 1]; + mbi1 = & beats[i]; + } + double time_dif = mbi1->time - mbi->time; + double beat_dif = mbi1->beat - mbi->beat; + return mbi->beat + (time - mbi->time) * beat_dif / time_dif; +} + + +void Alg_time_map::insert_beat(double time, double beat) +{ + int i = locate_time(time); // i is insertion point + if (i < beats.len && within(beats[i].time, time, 0.000001)) { + // replace beat if time is already in the map + beats[i].beat = beat; + } else { + Alg_beat point; + point.beat = beat; + point.time = time; + beats.insert(i, &point); + } + // beats[i] contains new beat + // make sure we didn't generate a zero tempo. + // if so, space beats by one microbeat as necessary + long j = i; + if (j == 0) j = 1; // do not adjust beats[0] + while (j < beats.len && + beats[j - 1].beat + 0.000001 >= beats[j].beat) { + beats[j].beat = beats[j - 1].beat + 0.000001; + j++; + } +} + + +bool Alg_time_map::insert_tempo(double tempo, double beat) +{ + tempo = tempo / 60.0; // convert to beats per second + // change the tempo at the given beat until the next beat event + if (beat < 0) return false; + double time = beat_to_time(beat); + long i = locate_time(time); + if (i >= beats.len || !within(beats[i].time, time, 0.000001)) { + insert_beat(time, beat); + } + // now i is index of beat where tempo will change + if (i == beats.len - 1) { + last_tempo = tempo; + // printf("last_tempo to %g\n", last_tempo); + last_tempo_flag = true; + } else { // adjust all future beats + // compute the difference in beats + double diff = beats[i + 1].beat - beats[i].beat; + // convert beat difference to seconds at new tempo + diff = diff / tempo; + // figure out old time difference: + double old_diff = beats[i + 1].time - time; + // compute difference too + diff = diff - old_diff; + // apply new_diff to score and beats + while (i < beats.len) { + beats[i].time = beats[i].time + diff; + i++; + } + } + return true; +} + + +bool Alg_time_map::set_tempo(double tempo, double start_beat, double end_beat) +{ + if (start_beat >= end_beat) return false; + // algorithm: insert a beat event if necessary at start_beat + // and at end_beat + // delete intervening map elements + // change the tempo + insert_beat(beat_to_time(start_beat), start_beat); + insert_beat(beat_to_time(end_beat), end_beat); + long start_x = locate_beat(start_beat) + 1; + long stop_x = locate_beat(end_beat); + while (stop_x < beats.len) { + beats[start_x] = beats[stop_x]; + start_x++; + stop_x++; + } + beats.len = start_x; // truncate the map to new length + return insert_tempo(tempo, start_beat); +} + + +void Alg_time_map::trim(double start, double end, bool units_are_seconds) +{ + // extract the time map from start to end and shift to time zero + // start and end are time in seconds if units_are_seconds is true + int i = 0; // index into beats + int start_index; // index of first breakpoint after start + int count = 1; + double initial_beat = start; + double final_beat = end; + if (units_are_seconds) { + initial_beat = time_to_beat(start); + final_beat = time_to_beat(end); + } else { + start = beat_to_time(initial_beat); + end = beat_to_time(final_beat); + } + while (i < length() && beats[i].time < start) i++; + // now i is index into beats of the first breakpoint after start + // beats[0] is (0,0) and remains that way + // copy beats[start_index] to beats[1], etc. + // skip any beats at or near (start,initial_beat), using count + // to keep track of how many entries there are + start_index = i; + while (i < length() && beats[i].time < end) { + if (beats[i].time - start > ALG_EPS && + beats[i].beat - initial_beat > ALG_EPS) { + beats[i].time = beats[i].time - start; + beats[i].beat = beats[i].beat - initial_beat; + beats[i - start_index + 1] = beats[i]; + count = count + 1; + } else { + start_index = start_index + 1; + } + i = i + 1; + } + // set last tempo data + // we last examined beats[i-1] and copied it to + // beats[i - start_index]. Next tempo should come + // from beats[i] and store in beats[i - start_index + 1] + // case 1: there is at least one breakpoint beyond end + // => interpolate to put a breakpoint at end + // case 2: no more breakpoints => set last tempo data + if (i < length()) { + // we know beats[i].time >= end, so case 1 applies + beats[i - start_index + 1].time = end - start; + beats[i - start_index + 1].beat = final_beat - initial_beat; + count = count + 1; + } + // else we'll just use stored last tempo (if any) + beats.len = count; +} + + +void Alg_time_map::cut(double start, double len, bool units_are_seconds) +{ + // remove portion of time map from start to start + len, + // shifting the tail left by len. start and len are in whatever + // units the score is in. If you cut the time_map as well as cut + // the tracks of the sequence, then sequences will preserve the + // association between tempo changes and events + double end = start + len; + double initial_beat = start; + double final_beat = end; + int i = 0; + + if (units_are_seconds) { + initial_beat = time_to_beat(start); + final_beat = time_to_beat(end); + } else { + start = beat_to_time(initial_beat); + end = beat_to_time(final_beat); + len = end - start; + } + double beat_len = final_beat - initial_beat; + + while (i < length() && beats[i].time < start - ALG_EPS) { + i = i + 1; + } + + // if no beats exist at or after start, just return; nothing to cut + if (i == length()) return; + + // now i is index into beats of the first breakpoint on or + // after start, insert (start, initial_beat) in map + if (i < length() && within(beats[i].time, start, ALG_EPS)) { + // perterb time map slightly (within alg_eps) to place + // break point exactly at the start time + beats[i].time = start; + beats[i].beat = initial_beat; + } else { + Alg_beat point(start, initial_beat); + beats.insert(i, &point); + } + // now, we're correct up to beats[i] and beats[i] happens at start. + // find first beat after end so we can start shifting from there + i = i + 1; + int start_index = i; + while (i < length() && beats[i].time < end + ALG_EPS) i++; + // now beats[i] is the next point to be included in beats + // but from i onward, we must shift by (-len, -beat_len) + while (i < length()) { + Alg_beat &b = beats[i]; + b.time = b.time - len; + b.beat = b.beat - beat_len; + beats[start_index] = b; + i = i + 1; + start_index = start_index + 1; + } + beats.len = start_index; +} + + +void Alg_time_map::paste(double beat, Alg_track *tr) +{ + // insert a given time map at a given time and dur (in beats) + Alg_time_map_ptr from_map = tr->get_time_map(); + // printf("time map paste\nfrom map\n"); + // from_map->show(); + // printf("to map\n"); + // show(); + Alg_beats &from = from_map->beats; + double time = beat_to_time(beat); + // Locate the point at which dur occurs + double dur = tr->get_beat_dur(); + double tr_end_time = from_map->beat_to_time(dur); + // add offset to make room for insert + int i = locate_beat(beat); + while (i < length()) { + beats[i].beat += dur; + beats[i].time += tr_end_time; + i++; + } + // printf("after opening up\n"); + // show(); + // insert point at beginning and end of paste + insert_beat(time, beat); + // printf("after beginning point insert\n"); + // show(); + // insert_beat(time + tr_end_time, beat + dur); + // printf("after ending point insert\n"); + // show(); + int j = from_map->locate_beat(dur); + for (i = 0; i < j; i++) { + insert_beat(from[i].time + time, // shift by time + from[i].beat + beat); // and beat + } + // printf("after inserts\n"); + show(); +} + + +void Alg_time_map::insert_time(double start, double len) +{ + // find time,beat pair that determines tempo at start + // compute beat offset = (delta beat / delta time) * len + // add len,beat offset to each following Alg_beat + // show(); + int i = locate_time(start); // start <= beats[i].time + if (beats[i].time == start) i++; // start < beats[i].time + // case 1: between beats + if (i > 0 && i < length()) { + double beat_offset = len * (beats[i].beat - beats[i-1].beat) / + (beats[i].time - beats[i-1].time); + while (i < length()) { + beats[i].beat += beat_offset; + beats[i].time += len; + i++; + } + } // otherwise, last tempo is in effect; nothing to do + // printf("time_map AFTER INSERT\n"); + // show(); +} + + +void Alg_time_map::insert_beats(double start, double len) +{ + int i = locate_beat(start); // start <= beats[i].beat + if (beats[i].beat == start) i++; + if (i > 0 && i < length()) { + double time_offset = len * (beats[i].time - beats[i-1].time) / + (beats[i].beat - beats[i-1].beat); + while (i < length()) { + beats[i].time += time_offset; + beats[i].beat += len; + i++; + } + } // otherwise, last tempo is in effect; nothing to do + // printf("time_map AFTER INSERT\n"); + // show(); +} + + +Alg_track::Alg_track(Alg_time_map *map, bool seconds) +{ + type = 't'; + time_map = NULL; + units_are_seconds = seconds; + set_time_map(map); +} + + +Alg_event_ptr Alg_track::copy_event(Alg_event_ptr event) +{ + Alg_event *new_event; + if (event->is_note()) { + new_event = new Alg_note((Alg_note_ptr) event); + } else { // update + new_event = new Alg_update((Alg_update_ptr) event); + } + return new_event; +} + + +Alg_track::Alg_track(Alg_track &track) +{ + type = 't'; + time_map = NULL; + for (int i = 0; i < track.length(); i++) { + append(copy_event(track.events[i])); + } + set_time_map(track.time_map); + units_are_seconds = track.units_are_seconds; +} + + +Alg_track::Alg_track(Alg_event_list_ref event_list, Alg_time_map_ptr map, + bool units_are_seconds) +{ + type = 't'; + time_map = NULL; + for (int i = 0; i < event_list.length(); i++) { + append(copy_event(event_list[i])); + } + set_time_map(map); + this->units_are_seconds = units_are_seconds; +} + + +void Alg_track::serialize(void **buffer, long *bytes) +{ + // first determine whether this is a seq or a track. + // if it is a seq, then we will write the time map and a set of tracks + // if it is a track, we just write the track data and not the time map + // + // The code will align doubles on ALIGN boundaries, and longs and + // floats are aligned to multiples of 4 bytes. + // + // The format for a seq is: + // 'ALGS' -- indicates that this is a sequence + // long length of all seq data in bytes starting with 'ALGS' + // long channel_offset_per_track + // long units_are_seconds + // time_map: + // double last_tempo + // long last_tempo_flag + // long len -- number of tempo changes + // for each tempo change (Alg_beat): + // double time + // double beat + // time_sigs: + // long len -- number of time_sigs + // long pad + // for each time signature: + // double beat + // double num + // double den + // tracks: + // long len -- number of tracks + // long pad + // for each track: + // 'ALGT' -- indicates this is a track + // long length of all track data in bytes starting with 'ALGT' + // long units_are_seconds + // double beat_dur + // double real_dur + // long len -- number of events + // for each event: + // long selected + // long type + // long key + // long channel + // double time + // if this is a note: + // double pitch + // double dur + // double loud + // long len -- number of parameters + // for each parameter: + // char attribute[] with zero pad to ALIGN + // one of the following, depending on type: + // double r + // char s[] terminated by zero + // long i + // long l + // char a[] terminated by zero + // zero pad to ALIGN + // else if this is an update + // (same representation as parameter above) + // zero pad to ALIGN + // + // The format for a track is given within the Seq format above + assert(get_type() == 't'); + ser_buf.init_for_write(); + serialize_track(); + *buffer = ser_buf.to_heap(bytes); +} + + +void Alg_seq::serialize(void **buffer, long *bytes) +{ + assert(get_type() == 's'); + ser_buf.init_for_write(); + serialize_seq(); + *buffer = ser_buf.to_heap(bytes); +} + + +void Serial_buffer::check_buffer(long needed) +{ + if (len < (ptr - buffer) + needed) { // do we need more space? + long new_len = len * 2; // exponential growth is important + // initially, length is zero, so bump new_len to a starting value + if (new_len == 0) new_len = 1024; + // make sure new_len is as big as needed + if (needed > new_len) new_len = needed; + char *new_buffer = new char[new_len]; // allocate space + memcpy(new_buffer, buffer, len); // copy from old buffer + ptr = new_buffer + (ptr - buffer); // relocate ptr to new buffer + delete buffer; // free old buffer + buffer = new_buffer; // update buffer information + len = new_len; + } +} + + +void Alg_seq::serialize_seq() +{ + int i; // loop counters + // we can easily compute how much buffer space we need until we + // get to tracks, so expand at least that much + long needed = 48 + 16 * time_map->beats.len + 24 * time_sig.length(); + ser_buf.check_buffer(needed); + ser_buf.set_char('A'); + ser_buf.set_char('L'); + ser_buf.set_char('G'); + ser_buf.set_char('S'); + long length_offset = ser_buf.get_posn(); + ser_buf.set_int32(0); // leave room to come back and write length + ser_buf.set_int32(channel_offset_per_track); + ser_buf.set_int32(units_are_seconds); + ser_buf.set_double(time_map->last_tempo); + ser_buf.set_int32(time_map->last_tempo_flag); + ser_buf.set_int32(time_map->beats.len); + for (i = 0; i < time_map->beats.len; i++) { + ser_buf.set_double(time_map->beats[i].time); + ser_buf.set_double(time_map->beats[i].beat); + } + ser_buf.set_int32(time_sig.length()); + ser_buf.pad(); + for (i = 0; i < time_sig.length(); i++) { + ser_buf.set_double(time_sig[i].beat); + ser_buf.set_double(time_sig[i].num); + ser_buf.set_double(time_sig[i].den); + } + ser_buf.set_int32(tracks()); + ser_buf.pad(); + for (i = 0; i < tracks(); i++) { + track(i)->serialize_track(); + } + // do not include ALGS, include padding at end + ser_buf.store_long(length_offset, ser_buf.get_posn() - length_offset); +} + + +void Alg_track::serialize_track() +{ + // to simplify the code, copy from parameter addresses to locals + int j; + ser_buf.check_buffer(32); + ser_buf.set_char('A'); + ser_buf.set_char('L'); + ser_buf.set_char('G'); + ser_buf.set_char('T'); + long length_offset = ser_buf.get_posn(); // save location for track length + ser_buf.set_int32(0); // room to write track length + ser_buf.set_int32(units_are_seconds); + ser_buf.set_double(beat_dur); + ser_buf.set_double(real_dur); + ser_buf.set_int32(len); + for (j = 0; j < len; j++) { + ser_buf.check_buffer(24); + Alg_event *event = (*this)[j]; + ser_buf.set_int32(event->get_selected()); + ser_buf.set_int32(event->get_type()); + ser_buf.set_int32(event->get_identifier()); + ser_buf.set_int32(event->chan); + ser_buf.set_double(event->time); + if (event->is_note()) { + ser_buf.check_buffer(20); + Alg_note *note = (Alg_note *) event; + ser_buf.set_float(note->pitch); + ser_buf.set_float(note->loud); + ser_buf.set_double(note->dur); + long parm_num_offset = ser_buf.get_posn(); + long parm_num = 0; + ser_buf.set_int32(0); // placeholder for no. parameters + Alg_parameters_ptr parms = note->parameters; + while (parms) { + serialize_parameter(&(parms->parm)); + parms = parms->next; + parm_num++; + } + ser_buf.store_long(parm_num_offset, parm_num); + } else { + assert(event->is_update()); + Alg_update *update = (Alg_update *) event; + serialize_parameter(&(update->parameter)); + } + ser_buf.check_buffer(7); // maximum padding possible + ser_buf.pad(); + } + // write length, not including ALGT, including padding at end + ser_buf.store_long(length_offset, ser_buf.get_posn() - length_offset); +} + + +void Alg_track::serialize_parameter(Alg_parameter *parm) +{ + // add eight to account for name + zero end-of-string and the + // possibility of adding 7 padding bytes + long len = strlen(parm->attr_name()) + 8; + ser_buf.check_buffer(len); + ser_buf.set_string(parm->attr_name()); + ser_buf.pad(); + switch (parm->attr_type()) { + case 'r': + ser_buf.check_buffer(8); + ser_buf.set_double(parm->r); + break; + case 's': + ser_buf.check_buffer(strlen(parm->s) + 1); + ser_buf.set_string(parm->s); + break; + case 'i': + ser_buf.check_buffer(4); + ser_buf.set_int32(parm->i); + break; + case 'l': + ser_buf.check_buffer(4); + ser_buf.set_int32(parm->l); + break; + case 'a': + ser_buf.check_buffer(strlen(parm->a) + 1); + ser_buf.set_string(parm->a); + break; + } +} + + + +Alg_track *Alg_track::unserialize(void *buffer, long len) +{ + assert(len > 8); + ser_buf.init_for_read(buffer, len); + bool alg = ser_buf.get_char() == 'A' && + ser_buf.get_char() == 'L' && + ser_buf.get_char() == 'G'; + assert(alg); + char c = ser_buf.get_char(); + if (c == 'S') { + Alg_seq *seq = new Alg_seq; + seq->unserialize_seq(); + return seq; + } else { + assert(c == 'T'); + Alg_track *track = new Alg_track; + track->unserialize_track(); + return track; + } +} + + +void Alg_seq::unserialize_seq() +{ + ser_buf.check_input_buffer(28); + long len = ser_buf.get_int32(); + assert(ser_buf.get_len() >= len); + channel_offset_per_track = ser_buf.get_int32(); + units_are_seconds = (bool) ser_buf.get_int32(); + time_map = new Alg_time_map(); + time_map->last_tempo = ser_buf.get_double(); + time_map->last_tempo_flag = (bool) ser_buf.get_int32(); + long beats = ser_buf.get_int32(); + ser_buf.check_input_buffer(beats * 16 + 4); + int i; + for (i = 0; i < beats; i++) { + double time = ser_buf.get_double(); + double beat = ser_buf.get_double(); + time_map->insert_beat(time, beat); + // printf("time_map: %g, %g\n", time, beat); + } + long time_sig_len = ser_buf.get_int32(); + ser_buf.get_pad(); + ser_buf.check_input_buffer(time_sig_len * 24 + 8); + for (i = 0; i < time_sig_len; i++) { + double beat = ser_buf.get_double(); + double num = ser_buf.get_double(); + double den = ser_buf.get_double(); + time_sig.insert(beat, num, den); + } + long tracks_num = ser_buf.get_int32(); + ser_buf.get_pad(); + add_track(tracks_num - 1); // create tracks_num tracks + for (i = 0; i < tracks_num; i++) { + track(i)->unserialize_track(); + } + // assume seq started at beginning of buffer. len measures + // bytes after 'ALGS' header, so add 4 bytes and compare to + // current buffer position -- they should agree + assert(ser_buf.get_posn() == len + 4); +} + + +void Alg_track::unserialize_track() +{ + ser_buf.check_input_buffer(32); + assert(ser_buf.get_char() == 'A'); + assert(ser_buf.get_char() == 'L'); + assert(ser_buf.get_char() == 'G'); + assert(ser_buf.get_char() == 'T'); + long offset = ser_buf.get_posn(); // stored length does not include 'ALGT' + long bytes = ser_buf.get_int32(); + assert(bytes <= ser_buf.get_len() - offset); + units_are_seconds = (bool) ser_buf.get_int32(); + beat_dur = ser_buf.get_double(); + real_dur = ser_buf.get_double(); + int event_count = ser_buf.get_int32(); + for (int i = 0; i < event_count; i++) { + ser_buf.check_input_buffer(24); + long selected = ser_buf.get_int32(); + char type = (char) ser_buf.get_int32(); + long key = ser_buf.get_int32(); + long channel = ser_buf.get_int32(); + double time = ser_buf.get_double(); + if (type == 'n') { + ser_buf.check_input_buffer(20); + float pitch = ser_buf.get_float(); + float loud = ser_buf.get_float(); + double dur = ser_buf.get_double(); + Alg_note *note = + create_note(time, channel, key, pitch, loud, dur); + note->set_selected(selected); + long param_num = ser_buf.get_int32(); + int j; + // this builds a list of parameters in the correct order + // (although order shouldn't matter) + Alg_parameters_ptr *list = ¬e->parameters; + for (j = 0; j < param_num; j++) { + *list = new Alg_parameters(NULL); + unserialize_parameter(&((*list)->parm)); + list = &((*list)->next); + } + append(note); + } else { + assert(type == 'u'); + Alg_update *update = create_update(time, channel, key); + update->set_selected(selected); + unserialize_parameter(&(update->parameter)); + append(update); + } + ser_buf.get_pad(); + } + assert(offset + bytes == ser_buf.get_posn()); +} + + +void Alg_track::unserialize_parameter(Alg_parameter_ptr parm_ptr) +{ + char *attr = ser_buf.get_string(); + parm_ptr->attr = symbol_table.insert_string(attr); + switch (parm_ptr->attr_type()) { + case 'r': + ser_buf.check_input_buffer(8); + parm_ptr->r = ser_buf.get_double(); + break; + case 's': + parm_ptr->s = heapify(ser_buf.get_string()); + break; + case 'i': + ser_buf.check_input_buffer(4); + parm_ptr->i = ser_buf.get_int32(); + break; + case 'l': + ser_buf.check_input_buffer(4); + parm_ptr->l = (bool) ser_buf.get_int32(); + break; + case 'a': + parm_ptr->a = symbol_table.insert_attribute(ser_buf.get_string()); + break; + } +} + + +void Alg_track::set_time_map(Alg_time_map *map) +{ + if (time_map) time_map->dereference(); + if (map == NULL) { + time_map = new Alg_time_map(); // new default map + time_map->reference(); + } else { + time_map = map; + time_map->reference(); + } +} + + +void Alg_track::convert_to_beats() +// modify all times and durations in notes to beats +{ + if (units_are_seconds) { + units_are_seconds = false; + long i; + + for (i = 0; i < length(); i++) { + Alg_event_ptr e = events[i]; + double beat = time_map->time_to_beat(e->time); + if (e->is_note()) { + Alg_note_ptr n = (Alg_note_ptr) e; + n->dur = time_map->time_to_beat(n->time + n->dur) - beat; + } + e->time = beat; + } + } +} + + +void Alg_track::convert_to_seconds() +// modify all times and durations in notes to seconds +{ + if (!units_are_seconds) { + last_note_off = time_map->beat_to_time(last_note_off); + units_are_seconds = true; + long i; + for (i = 0; i < length(); i++) { + Alg_event_ptr e = events[i]; + double time = time_map->beat_to_time(e->time); + if (e->is_note()) { + Alg_note_ptr n = (Alg_note_ptr) e; + n->dur = time_map->beat_to_time(n->time + n->dur) - time; + } + e->time = time; + } + } +} + + +void Alg_track::set_dur(double duration) +{ + // set beat_dur and real_dur + if (units_are_seconds) { + set_real_dur(duration); + set_beat_dur(time_map->time_to_beat(duration)); + } else { + set_beat_dur(duration); + set_real_dur(time_map->beat_to_time(duration)); + } +} + + +Alg_note *Alg_track::create_note(double time, int channel, int identifier, + float pitch, float loudness, double duration) +{ + Alg_note *note = new Alg_note(); + note->time = time; + note->chan = channel; + note->set_identifier(identifier); + note->pitch = pitch; + note->loud = loudness; + note->dur = duration; + return note; +} + + +Alg_update *Alg_track::create_update(double time, int channel, int identifier) +{ + Alg_update *update = new Alg_update(); + update->time = time; + update->chan = channel; + update->set_identifier(identifier); + return update; +} + + +Alg_track_ptr Alg_track::cut(double t, double len, bool all) +{ + // since we are translating notes in time, do not copy or use old timemap + Alg_track_ptr track = new Alg_track(); + track->units_are_seconds = units_are_seconds; + if (units_are_seconds) { + track->set_real_dur(len); + track->set_beat_dur(time_map->time_to_beat(t + len) - + time_map->time_to_beat(t)); + } else { + track->set_beat_dur(len); + track->set_real_dur(time_map->beat_to_time(t + len) - + time_map->beat_to_time(t)); + } + int i; + int new_len = 0; + int change = 0; + for (i = 0; i < length(); i++) { + Alg_event_ptr event = events[i]; + if (event->overlap(t, len, all)) { + event->time -= t; + track->append(event); + change = 1; + } else { // if we're not cutting this event, move it to + // eliminate the gaps in events left by cut events + events[new_len] = event; + // adjust times of events after t + len + if (event->time > t + len - ALG_EPS) { + event->time -= len; + change = 1; + } + new_len++; + } + } + // Alg_event_lists based on this track become invalid + sequence_number += change; + this->len = new_len; // adjust length since we removed events + return track; +} + + +Alg_track_ptr Alg_track::copy(double t, double len, bool all) +{ + // since we are translating notes in time, do not copy or use old timemap + Alg_track_ptr track = new Alg_track(); + track->units_are_seconds = units_are_seconds; + if (units_are_seconds) { + track->set_real_dur(len); + track->set_beat_dur(time_map->time_to_beat(t + len) - + time_map->time_to_beat(t)); + } else { + track->set_beat_dur(len); + track->set_real_dur(time_map->beat_to_time(t + len) - + time_map->beat_to_time(t)); + } + int i; + for (i = 0; i < length(); i++) { + Alg_event_ptr event = events[i]; + if (event->overlap(t, len, all)) { + Alg_event_ptr new_event = copy_event(event); + new_event->time -= t; + track->append(new_event); + } + } + return track; +} + + +void Alg_track::paste(double t, Alg_event_list *seq) +{ + assert(get_type() == 't'); + // seq can be an Alg_event_list, an Alg_track, or an Alg_seq + // if it is an Alg_event_list, units_are_seconds must match + bool prev_units_are_seconds; + if (seq->get_type() == 'e') { + assert(seq->get_owner()->get_units_are_seconds() == units_are_seconds); + } else { // make it match + Alg_track_ptr tr = (Alg_track_ptr) seq; + prev_units_are_seconds = tr->get_units_are_seconds(); + if (units_are_seconds) tr->convert_to_seconds(); + else tr->convert_to_beats(); + } + double dur = (units_are_seconds ? seq->get_real_dur() : + seq->get_beat_dur()); + + // Note: in the worst case, seq may contain notes + // that start almost anytime up to it's duration, + // so the simplest algorithm is simply a sequence + // of inserts. If this turns out to be too slow, + // we can do a merge sort in the case that seq + // is an Alg_track (if it's an Alg_event_list, we + // are not guaranteed that the events are in time + // order, but currently, only a true seq is allowed) + + int i; + for (i = 0; i < length(); i++) { + if (events[i]->time > t - ALG_EPS) { + events[i]->time += dur; + } + } + for (i = 0; i < seq->length(); i++) { + Alg_event *new_event = copy_event((*seq)[i]); + new_event->time += t; + insert(new_event); + } + // restore track units to what they were before + if (seq->get_type() != 'e') { + Alg_track_ptr tr = (Alg_track_ptr) seq; + if (prev_units_are_seconds) tr->convert_to_seconds(); + else tr->convert_to_beats(); + } + +} + + +void Alg_track::merge(double t, Alg_event_list_ptr seq) +{ + Alg_event_list_ref s = *seq; + for (int i = 0; i < s.length(); i++) { + Alg_event *new_event; + if (s[i]->is_note()) { + new_event = new Alg_note((Alg_note_ptr) s[i]); + } else { + new_event = new Alg_update((Alg_update_ptr) s[i]); + } + new_event->time += t; + insert(new_event); + } +} + + +void Alg_track::clear(double t, double len, bool all) +{ + int i; + int move_to = 0; + for (i = 0; i < length(); i++) { + Alg_event_ptr event = events[i]; + if (event->overlap(t, len, all)) { + delete events[i]; + } else { // if we're not clearing this event, move it to + // eliminate the gaps in events left by cleared events + events[move_to] = event; + // adjust times of events after t + len. This test is based + // on the one in Alg_event::overlap() for consistency. + if (event->time > t + len - ALG_EPS && event->time > t) + event->time -= len; + move_to++; + } + } + if (move_to != this->len) { // we cleared at least one note + sequence_number++; // Alg_event_lists based on this track become invalid + } + this->len = move_to; // adjust length since we removed events +} + + +void Alg_track::silence(double t, double len, bool all) +{ + int i; + int move_to = 0; + for (i = 0; i < length(); i++) { + Alg_event_ptr event = events[i]; + if (event->overlap(t, len, all)) { + delete events[i]; + } else { // if we're not clearing this event, move it to + // eliminate the gaps in events left by cleared events + events[move_to] = event; + move_to++; + } + } + if (move_to != this->len) { // we cleared at least one note + sequence_number++; // Alg_event_lists based on this track become invalid + } + this->len = move_to; // adjust length since we removed events +} + + +void Alg_track::insert_silence(double t, double len) +{ + int i; + for (i = 0; i < length(); i++) { + Alg_event_ptr event = events[i]; + if (event->time > t - ALG_EPS) event->time += len; + } +} + + +Alg_event_list *Alg_track::find(double t, double len, bool all, + long channel_mask, long event_type_mask) +{ + int i; + Alg_event_list *list = new Alg_event_list(this); + if (units_are_seconds) { // t and len are seconds + list->set_real_dur(len); + list->set_beat_dur(get_time_map()->time_to_beat(t + len) - + get_time_map()->time_to_beat(t)); + } else { // t and len are beats + list->set_real_dur(get_time_map()->beat_to_time(t + len) - + get_time_map()->beat_to_time(t)); + list->set_beat_dur(len); + } + for (i = 0; i < length(); i++) { + Alg_event_ptr event = events[i]; + if (event->overlap(t, len, all)) { + if ((channel_mask == 0 || + (event->chan < 32 && + (channel_mask & (1 << event->chan)))) && + ((event_type_mask == 0 || + (event_type_mask & (1 << event->get_type_code()))))) { + list->append(event); + } + } + } + return list; +} + + +void Alg_time_sigs::expand() +{ + maxlen = (maxlen + 5); // extra growth for small sizes + maxlen += (maxlen >> 2); // add 25% + Alg_time_sig_ptr new_time_sigs = new Alg_time_sig[maxlen]; + // now do copy + memcpy(new_time_sigs, time_sigs, len * sizeof(Alg_time_sig)); + if (time_sigs) delete[] time_sigs; + time_sigs = new_time_sigs; +} + + +void Alg_time_sigs::insert(double beat, double num, double den) +{ + // find insertion point: + for (int i = 0; i < len; i++) { + if (within(time_sigs[i].beat, beat, ALG_EPS)) { + // overwrite location i with new info + time_sigs[i].beat = beat; + time_sigs[i].num = num; + time_sigs[i].den = den; + return; + } else if (time_sigs[i].beat > beat) { + if ((i > 0 && // check if redundant with prev. time sig + time_sigs[i - 1].num == num && + time_sigs[i - 1].den == den && + within(fmod(beat - time_sigs[i - 1].beat, + 4 * time_sigs[i-1].num / time_sigs[i-1].den), + 0, ALG_EPS)) || + // check if redundant with implied initial 4/4 time sig: + (i == 0 && num == 4 && den == 4 && + within(fmod(beat, 4), 0, ALG_EPS))) { + return; // redundant inserts are ignored here + } + // make room for new event + if (maxlen <= len) expand(); + len++; + // insert new event at i + memmove(&time_sigs[i + 1], &time_sigs[i], + sizeof(Alg_time_sig) * (len - i)); + time_sigs[i].beat = beat; + time_sigs[i].num = num; + time_sigs[i].den = den; + return; + } + } + // if we fall out of loop, then this goes at end + if (maxlen <= len) expand(); + time_sigs[len].beat = beat; + time_sigs[len].num = num; + time_sigs[len].den = den; + len++; +} + + +void Alg_time_sigs::show() +{ + printf("Alg_time_sig: "); + for (int i = 0; i < len; i++) { + printf("(%g: %g/%g) ", time_sigs[i].beat, time_sigs[i].num, time_sigs[i].den); + } + printf("\n"); +} + + +int Alg_time_sigs::find_beat(double beat) +{ + // index where you would insert a new time signature at beat + int i = 0; + while (i < len && time_sigs[i].beat < beat - ALG_EPS) i++; + return i; +} + + +void Alg_time_sigs::cut(double start, double end) +{ + // remove time_sig's from start to start+len -- these must be + // in beats (not seconds) + // now rewrite time_sig[]: copy from i_in to i_out (more or less) + int i_in = 0; + int i_out = 0; + // first, figure out where to begin cut region + i_in = find_beat(start); + i_out = i_in; + // scan to end of cut region + while (i_in < len && time_sigs[i_in].beat < end) { + i_in = i_in + 1; + } + // change time_sig at start if necessary + // there's a time_sig that was skipped if i_in > i_out. + // if that's true and the next time change is at end, we're + // ok because it will be copied, but if the next time change + // is after end, then maybe we should insert a time change + // corresponding to what's in effect at end. We can skip this + // insert if it corresponds to whatever is in effect at start + if (i_in > i_out && i_in < len && + time_sigs[i_in].beat > end + ALG_EPS && + (i_out == 0 || time_sigs[i_out - 1].num != time_sigs[i_in - 1].num || + time_sigs[i_out - 1].den != time_sigs[i_in - 1].den)) { + time_sigs[i_out] = time_sigs[i_in - 1]; + time_sigs[i_out].beat = start; + } + // scan from end to len(time_sig) + while (i_in < length()) { + Alg_time_sig &ts = time_sigs[i_in]; + ts.beat = ts.beat - (end - start); + time_sigs[i_out] = ts; + i_in = i_in + 1; + i_out = i_out + 1; + } + len = i_out; +} + + +void Alg_time_sigs::trim(double start, double end) +{ + // remove time_sig's not in [start, start+end) + // units must be in beats (not seconds) + // copy from i_in to i_out as we scan time_sig array + int i_in = 0; + int i_out = 0; + // first, skip time signatures up to start + i_in = find_beat(start); + // put time_sig at start if necessary + // if 0 < i_in < len, then the time sig at i_in is either + // at start or after start. + // If after start, then insert time sig at i_in-1 at 0. + // Otherwise, we'll pick up time sig at i_in below. + // If 0 == i_in < len, then the time sig at i_in is either + // at start or after start. + // If after start, then time sig at 0 is 4/4, but that's the + // default, so do nothing. + // Otherwise, we'll pick up time sig at i_in below. + // If 0 < i_in == len, then insert time_sig at i_in-1 at start + // If 0 == i_in == len, then 4/4 default applies and we're done. + // + // So the conditions for inserting time_sig[in_i-1] at 0 are: + // (0 < i_in < len and time_sig[i] > start+ALG_EPS) OR + // (0 < i_in == len) + // We can rewrite this to + // (0 < i_in) && ((i_in < len && time_sig[i_in].beat > start + ALG_EPS) || + // (i_in == len))) + // + if (0 < i_in && ((i_in < len && time_sigs[i_in].beat > start + ALG_EPS) || + (i_in == len))) { + time_sigs[0] = time_sigs[i_in - 1]; + time_sigs[0].beat = 0.0; + i_out = 1; + } + // scan to end of cut region + while (i_in < len && time_sigs[i_in].beat < end - ALG_EPS) { + Alg_time_sig &ts = time_sigs[i_in]; + ts.beat = ts.beat - start; + time_sigs[i_out] = ts; + i_in++; + i_out++; + } + len = i_out; +} + + +void Alg_time_sigs::paste(double start, Alg_seq *seq) +{ + // printf("time_sig::insert before paste\n"); + // show(); + Alg_time_sigs &from = seq->time_sig; + // printf("time_sig::insert from\n"); + from.show(); + // insert time signatures from seq into this time_sigs at start + if (len == 0 && from.len == 0) { + return; // default applies + } + int i = find_beat(start); + // remember the time signature at the splice point + double num_after_splice = 4; + double den_after_splice = 4; // default + // three cases: + // 1) time sig at splice is at i-1 + // for this, we must have len>0 & i>0 + // two sub-cases: + // A) i < len && time_sig[i].beat > start + // B) i == len + // 2) time_sig at splice is at i + // for this, i < len && time_sig[i].beat ~= start + // 3) time_sig at splice is default 4/4 + if (len > 0 && i > 0 && + ((i < len && time_sigs[i].beat > start + ALG_EPS) || + (i == len))) { + num_after_splice = time_sigs[i-1].num; + den_after_splice = time_sigs[i-1].den; + } else if (i < len && time_sigs[i].beat <= start + ALG_EPS) { + num_after_splice = time_sigs[i].num; + den_after_splice = time_sigs[i].den; + } + // i is where insert will go, time_sig[i].beat > start + // begin by adding duration to time_sig's at i and above + // move time signatures forward by duration of seq + double dur = seq->get_beat_dur(); + while (i < len) { + time_sigs[i].beat += dur; + i++; + } + //printf("time_sig::insert after making space\n"); + //show(); + // now insert initial time_signature at start. This may create + // an extra measure if seq does not begin on a measure boundary + insert(start, 4, 4); // in case seq uses default starting signature + //printf("time_sig::insert after 4/4 at start\n"); + //show(); + // insert time signatures from seq offset by start + for (i = 0; i < from.length(); i++) { + insert(start + from[i].beat, from[i].num, from[i].den); + } + //printf("time_sig::insert after pasting in sigs\n"); + //show(); + // now insert time signature at end of splice + insert(start + dur, num_after_splice, den_after_splice); + //printf("time_sig::insert after sig at end of splice\n"); + //show(); +} + + +void Alg_time_sigs::insert_beats(double beat, double len) +{ + int i; + // find the time_sig entry in effect at t + for (i = 0; i < len; i++) { + if (time_sigs[i].beat < beat + ALG_EPS) { + break; + } + } + // now, increase beat times by len + for (; i < len; i++) { + time_sigs[i].beat += len; + } +} + + +Alg_tracks::~Alg_tracks() +{ + // Alg_events objects (track data) are not deleted, only the array + if (tracks) { + delete[] tracks; + } +} + + +void Alg_tracks::expand_to(int new_max) +{ + maxlen = new_max; + Alg_track_ptr *new_tracks = new Alg_track_ptr[maxlen]; + // now do copy + memcpy(new_tracks, tracks, len * sizeof(Alg_track_ptr)); + if (tracks) { + delete[] tracks; + } + tracks = new_tracks; +} + + +void Alg_tracks::expand() +{ + maxlen = (maxlen + 5); // extra growth for small sizes + maxlen += (maxlen >> 2); // add 25% + expand_to(maxlen); +} + + +void Alg_tracks::append(Alg_track_ptr track) +{ + if (maxlen <= len) { + expand(); + } + tracks[len] = track; + len++; +} + + +void Alg_tracks::add_track(int track_num, Alg_time_map_ptr time_map, + bool seconds) + // Create a new track at index track_num. + // If track already exists, this call does nothing. + // If highest previous track is not at track_num-1, then + // create tracks at len, len+1, ..., track_num. +{ + assert(track_num >= 0); + if (track_num == maxlen) { + // use eponential growth to insert tracks sequentially + expand(); + } else if (track_num > maxlen) { + // grow to exact size for random inserts + expand_to(track_num + 1); + } + if (track_num < len) return; // don't add if already there + while (len <= track_num) { + tracks[len] = new Alg_track(time_map, seconds); + //printf("allocated track at %d (%x, this %x) = %x\n", len, + // &(tracks[len]), this, tracks[len]); + len++; + } +} + + +void Alg_tracks::reset() +{ + // all track events are incorporated into the seq, + // so all we need to delete are the arrays of pointers + for (int i = 0; i < len; i++) { + printf("deleting track at %d (%x, this %x) = %x\n", i, &(tracks[i]), + this, tracks[i]); + delete tracks[i]; + } + if (tracks) delete [] tracks; + tracks = NULL; + len = 0; + maxlen = 0; // Modified by Ning Hu Nov.19 2002 +} + + +Alg_seq::Alg_seq(const char *filename, bool smf) +{ + basic_initialization(); + ifstream inf(filename, smf ? ios::binary | ios::in : ios::in); + if (inf.fail()) { + error = alg_error_open; + return; + } + if (smf) { + error = alg_smf_read(inf, this); + } else { + error = alg_read(inf, this); + } + inf.close(); +} + + +Alg_seq::Alg_seq(istream &file, bool smf) +{ + basic_initialization(); + if (smf) { + error = alg_smf_read(file, this); + } else { + error = alg_read(file, this); + } +} + +void Alg_seq::seq_from_track(Alg_track_ref tr) +{ + type = 's'; + // copy everything + set_beat_dur(tr.get_beat_dur()); + set_real_dur(tr.get_real_dur()); + // copy time_map + set_time_map(new Alg_time_map(tr.get_time_map())); + units_are_seconds = tr.get_units_are_seconds(); + + if (tr.get_type() == 's') { + Alg_seq_ref s = *(tr.to_alg_seq()); + channel_offset_per_track = s.channel_offset_per_track; + add_track(s.tracks() - 1); + // copy each track + for (int i = 0; i < tracks(); i++) { + Alg_track_ref from_track = *(s.track(i)); + Alg_track_ref to_track = *(track(i)); + to_track.set_beat_dur(from_track.get_beat_dur()); + to_track.set_real_dur(from_track.get_real_dur()); + if (from_track.get_units_are_seconds()) + to_track.convert_to_seconds(); + for (int j = 0; j < from_track.length(); j++) { + Alg_event_ptr event = copy_event(from_track[j]); + to_track.append(event); + } + } + } else if (tr.get_type() == 't') { + add_track(0); + channel_offset_per_track = 0; + Alg_track_ptr to_track = track(0); + to_track->set_beat_dur(tr.get_beat_dur()); + to_track->set_real_dur(tr.get_real_dur()); + for (int j = 0; j < tr.length(); j++) { + Alg_event_ptr event = copy_event(tr[j]); + to_track->append(event); + } + } else { + assert(false); // expected track or sequence + } +} + + +int Alg_seq::tracks() +{ + return track_list.length(); +} + + +Alg_track_ptr Alg_seq::track(int i) +{ + assert(0 <= i && i < track_list.length()); + return &(track_list[i]); +} + + +Alg_event_ptr &Alg_seq::operator[](int i) +{ + int ntracks = track_list.length(); + int tr = 0; + while (tr < ntracks) { + Alg_track *a_track = track(tr); + if (a_track && i < a_track->length()) { + return (*a_track)[i]; + } else if (a_track) { + i -= a_track->length(); + } + tr++; + } + assert(false); // out of bounds +} + + +void Alg_seq::convert_to_beats() +{ + if (!units_are_seconds) return; + for (int i = 0; i < tracks(); i++) { + track(i)->convert_to_beats(); + } + // note that the Alg_seq inherits units_are_seconds from an + // empty track. Each track also has a (redundant) field called + // units are seconds. These should always be consistent. + units_are_seconds = false; +} + + +void Alg_seq::convert_to_seconds() +{ + if (units_are_seconds) return; + //printf("convert_to_seconds, tracks %d\n", tracks()); + //printf("last_tempo of seq: %g on map %x\n", + // get_time_map()->last_tempo, get_time_map()); + for (int i = 0; i < tracks(); i++) { + //printf("last_tempo of track %d: %g on %x\n", i, + // track(i)->get_time_map()->last_tempo, + // track(i)->get_time_map()); + track(i)->convert_to_seconds(); + } + // update our copy of last_note_off (which may or may not be valid) + last_note_off = time_map->beat_to_time(last_note_off); + // note that the Alg_seq inherits units_are_seconds from an + // empty track. Each track also has a (redundant) field called + // units are seconds. These should always be consistent. + units_are_seconds = true; +} + + +Alg_track_ptr Alg_seq::cut_from_track(int track_num, double start, + double dur, bool all) +{ + assert(track_num >= 0 && track_num < tracks()); + Alg_track_ptr tr = track(track_num); + return tr->cut(start, dur, all); +} + + +void Alg_seq::copy_time_sigs_to(Alg_seq *dest) +{ + // copy time signatures + for (int i = 0; i < time_sig.length(); i++) { + dest->time_sig.insert(time_sig[i].beat, time_sig[i].num, + time_sig[i].den); + } +} + + +void Alg_seq::set_time_map(Alg_time_map *map) +{ + Alg_track::set_time_map(map); + for (int i = 0; i < tracks(); i++) { + track(i)->set_time_map(map); + } +} + + +Alg_seq_ptr Alg_seq::cut(double start, double len, bool all) + // return sequence from start to start+len and modify this + // sequence by removing that time-span +{ + // fix parameters to fall within existing sequence + if (start > get_dur()) return NULL; // nothing to cut + if (start < 0) start = 0; // can't start before sequence starts + if (start + len > get_dur()) // can't cut after end: + len = get_dur() - start; + + Alg_seq_ptr result = new Alg_seq(); + Alg_time_map_ptr map = new Alg_time_map(get_time_map()); + result->set_time_map(map); + copy_time_sigs_to(result); + result->units_are_seconds = units_are_seconds; + result->track_list.reset(); + + for (int i = 0; i < tracks(); i++) { + Alg_track_ptr cut_track = cut_from_track(i, start, len, all); + result->track_list.append(cut_track); + // initially, result->last_note_off is zero. We want to know the + // maximum over all cut_tracks, so compute that here: + result->last_note_off = MAX(result->last_note_off, + cut_track->last_note_off); + // since we're moving to a new sequence, change the track's time_map + result->track_list[i].set_time_map(map); + } + + // put units in beats to match time_sig's. Note that we need + // two different end times. For result, we want the time of the + // last note off, but for cutting out the time signatures in this, + // we use len. + double ts_start = start; + double ts_end = start + len; + double ts_last_note_off = start + result->last_note_off; + if (units_are_seconds) { + ts_start = time_map->time_to_beat(ts_start); + ts_end = time_map->time_to_beat(ts_end); + ts_last_note_off = time_map->time_to_beat(ts_last_note_off); + } + // result is shifted from start to 0 and has length len, but + // time_sig and time_map are copies from this. Adjust time_sig, + // time_map, and duration fields in result. The time_sig and + // time_map data is retained out to last_note_off so that we have + // information for the entire duration of all the notes, even though + // this might extend beyond the duration of the track. (Warning: + // no info is retained for notes with negative times.) + result->time_sig.trim(ts_start, ts_last_note_off); + result->time_map->trim(start, start + result->last_note_off, + result->units_are_seconds); + // even though there might be notes sticking out beyond len, the + // track duration is len, not last_note_off. (Warning: if all is + // true, there may also be notes at negative offsets. These times + // cannot be mapped between beat and time representations, so there + // may be subtle bugs or unexpected behaviors in that case.) + result->set_dur(len); + + // we sliced out a portion of each track, so now we need to + // slice out the corresponding sections of time_sig and time_map + // as well as to adjust the duration. + time_sig.cut(ts_start, ts_end); + time_map->cut(start, len, units_are_seconds); + set_dur(get_dur() - len); + + return result; +} + + +void Alg_seq::insert_silence_in_track(int track_num, double t, double len) +{ + Alg_track_ptr tr = track(track_num); + tr->insert_silence(t, len); +} + + +void Alg_seq::insert_silence(double t, double len) +{ + for (int i = 0; i < tracks(); i++) { + insert_silence_in_track(i, t, len); + } + double t_beats = t; + double len_beats = len; + // insert into time_sig array; use time_sig_paste, + // which requires us to build a simple time_sig array + if (units_are_seconds) { + time_map->insert_time(t, len); + t_beats = time_map->time_to_beat(t); + len_beats = time_map->time_to_beat(t + len) - t_beats; + } else { + time_map->insert_beats(t_beats, len_beats); + } + if (time_sig.length() > 0) { + time_sig.insert_beats(t_beats, len_beats); + } +} + + +Alg_track_ptr Alg_seq::copy_track(int track_num, double t, double len, bool all) +{ + return track_list[track_num].copy(t, len, all); +} + + +Alg_seq *Alg_seq::copy(double start, double len, bool all) +{ + // fix parameters to fall within existing sequence + if (start > get_dur()) return NULL; // nothing to copy + if (start < 0) start = 0; // can't copy before sequence starts + if (start + len > get_dur()) // can't copy after end: + len = get_dur() - start; + + // return (new) sequence from start to start + len + Alg_seq_ptr result = new Alg_seq(); + Alg_time_map_ptr map = new Alg_time_map(get_time_map()); + result->set_time_map(map); + copy_time_sigs_to(result); + result->units_are_seconds = units_are_seconds; + result->track_list.reset(); + + for (int i = 0; i < tracks(); i++) { + Alg_track_ptr copy = copy_track(i, start, len, all); + result->track_list.append(copy); + result->last_note_off = MAX(result->last_note_off, + copy->last_note_off); + // since we're copying to a new seq, change the track's time_map + result->track_list[i].set_time_map(map); + } + + // put units in beats to match time_sig's. Note that we need + // two different end times. For result, we want the time of the + // last note off, but for cutting out the time signatures in this, + // we use len. + double ts_start = start; + double ts_end = start + len; + double ts_last_note_off = start + result->last_note_off; + if (units_are_seconds) { + ts_start = time_map->time_to_beat(ts_start); + ts_end = time_map->time_to_beat(ts_end); + ts_last_note_off = time_map->time_to_beat(ts_last_note_off); + } + + result->time_sig.trim(ts_start, ts_last_note_off); + result->time_map->trim(start, start + result->last_note_off, + units_are_seconds); + result->set_dur(len); + return result; +} + + +void Alg_seq::paste(double start, Alg_seq *seq) +{ + // insert seq at time; open up space for it + // to manipulate time map, we need units as beats + // save original form so we can convert back if necessary + bool units_should_be_seconds = units_are_seconds; + bool seq_units_should_be_seconds = seq->get_units_are_seconds(); + if (units_are_seconds) { + start = time_map->time_to_beat(start); + convert_to_beats(); + } + seq->convert_to_beats(); + + // do a paste on each track + int i; + for (i = 0; i < seq->tracks(); i++) { + if (i >= tracks()) { + add_track(i); + } + track(i)->paste(start, seq->track(i)); + } + // make sure all tracks were opened up for an insert, even if + // there is nothing to insert + while (i < tracks()) { + track(i)->insert_silence(start, seq->get_dur()); + i++; + } + // paste in tempo track + time_map->paste(start, seq); + // paste in time signatures + time_sig.paste(start, seq); + set_dur(get_beat_dur() + seq->get_dur()); + assert(!seq->units_are_seconds && !units_are_seconds); + if (units_should_be_seconds) { + convert_to_seconds(); + } + if (seq_units_should_be_seconds) { + seq->convert_to_seconds(); + } +} + + +void Alg_seq::merge(double t, Alg_event_list_ptr seq) +{ + // seq must be an Alg_seq: + assert(seq->get_type() == 's'); + Alg_seq_ptr s = (Alg_seq_ptr) seq; + for (int i = 0; i < s->tracks(); i++) { + if (tracks() <= i) add_track(i); + track(i)->merge(t, s->track(i)); + } +} + + +void Alg_seq::silence_track(int track_num, double start, double len, bool all) +{ + // remove events in [time, time + len) and close gap + Alg_track_ptr tr = track(track_num); + tr->silence(start, len, all); +} + + +void Alg_seq::silence(double t, double len, bool all) +{ + for (int i = 0; i < tracks(); i++) { + silence_track(i, t, len, all); + } +} + + +void Alg_seq::clear_track(int track_num, double start, double len, bool all) +{ + // remove events in [time, time + len) and close gap + Alg_track_ptr tr = track(track_num); + tr->clear(start, len, all); +} + + +void Alg_seq::clear(double start, double len, bool all) +{ + // Fix parameters to fall within existing sequence + if (start > get_dur()) return; // nothing to cut + if (start < 0) start = 0; // can't start before sequence starts + if (start + len > get_dur()) // can't cut after end: + len = get_dur() - start; + + for (int i = 0; i < tracks(); i++) + clear_track(i, start, len, all); + + // Put units in beats to match time_sig's. + double ts_start = start; + double ts_end = start + len; + if (units_are_seconds) { + ts_start = time_map->time_to_beat(ts_start); + ts_end = time_map->time_to_beat(ts_end); + } + + // we sliced out a portion of each track, so now we need to + // slice out the corresponding sections of time_sig and time_map + // as well as to adjust the duration. + time_sig.cut(ts_start, ts_end); + time_map->cut(start, len, units_are_seconds); + set_dur(get_dur() - len); +} + + +Alg_event_list_ptr Alg_seq::find_in_track(int track_num, double t, double len, + bool all, long channel_mask, + long event_type_mask) +{ + return track(track_num)->find(t, len, all, channel_mask, event_type_mask); +} + + +Alg_seq::~Alg_seq() +{ + int i, j; + // Tracks does not delete Alg_events elements + for (j = 0; j < track_list.length(); j++) { + Alg_track ¬es = track_list[j]; + // Alg_events does not delete notes + for (i = 0; i < notes.length(); i++) { + Alg_event_ptr event = notes[i]; + delete event; + } + } +} + + +long Alg_seq::seek_time(double time, int track_num) +// find index of first score event after time +{ + long i; + Alg_events ¬es = track_list[track_num]; + for (i = 0; i < notes.length(); i++) { + if (notes[i]->time > time) { + break; + } + } + return i; +} + + +bool Alg_seq::insert_beat(double time, double beat) +// insert a time,beat pair +// return true or false (false indicates an error, no update) +// it is an error to imply a negative tempo or to insert at +// a negative time +{ + if (time < 0 || beat < 0) return false; + if (time == 0.0 && beat > 0) + time = 0.000001; // avoid infinite tempo, offset time by 1us + if (time == 0.0 && beat == 0.0) + return true; // (0,0) is already in the map! + convert_to_beats(); // beats are invariant when changing tempo + time_map->insert_beat(time, beat); + return true; +} + + +bool Alg_seq::insert_tempo(double bpm, double beat) +{ + double bps = bpm / 60.0; // convert to beats per second + // change the tempo at the given beat until the next beat event + if (beat < 0) return false; + convert_to_beats(); // beats are invariant when changing tempo + double time = time_map->beat_to_time(beat); + long i = time_map->locate_time(time); + if (i >= time_map->beats.len || !within(time_map->beats[i].time, time, 0.000001)) { + insert_beat(time, beat); + } + // now i is index of beat where tempo will change + if (i == time_map->beats.len - 1) { + time_map->last_tempo = bps; + time_map->last_tempo_flag = true; + } else { // adjust all future beats + // compute the difference in beats + double diff = time_map->beats[i + 1].beat - time_map->beats[i].beat; + // convert beat difference to seconds at new tempo + diff = diff / bps; + // figure out old time difference: + double old_diff = time_map->beats[i + 1].time - time; + // compute difference too + diff = diff - old_diff; + // apply new_diff to score and beats + while (i < time_map->beats.len) { + time_map->beats[i].time = time_map->beats[i].time + diff; + i++; + } + } + return true; +} + + +void Alg_seq::add_event(Alg_event_ptr event, int track_num) + // add_event puts an event in a given track (track_num). + // The track must exist. The time and duration of the + // event are interpreted according to whether the Alg_seq + // is currently in beats or seconds (see convert_to_beats()) +{ + track_list[track_num].insert(event); +/* + if (event->is_note()) { + Alg_note_ptr n = (Alg_note_ptr) event; + trace("note %d at %g for %g\n", n->get_identifier(), n->time, n->dur); + } + */ +} + + +bool Alg_seq::set_tempo(double bpm, double start_beat, double end_beat) +// set tempo from start_beat to end_beat +{ + // this is an optimization, the test is repeated in Alg_time_seq::set_tempo() + if (start_beat >= end_beat) return false; + bool units_should_be_seconds = units_are_seconds; + convert_to_beats(); + bool result = time_map->set_tempo(bpm, start_beat, end_beat); + if (units_should_be_seconds) convert_to_seconds(); + return result; +} + + +void Alg_seq::set_time_sig(double beat, double num, double den) +{ + time_sig.insert(beat, num, den); +} + + +void Alg_seq::beat_to_measure(double beat, long *measure, double *m_beat, + double *num, double *den) +{ + // return [measure, beat, num, den] + double m = 0; // measure number + double bpm; + int tsx; + bpm = 4; + // assume 4/4 if no time signature + double prev_beat = 0; + double prev_num = 4; + double prev_den = 4; + + if (beat < 0) beat = 0; // negative measures treated as zero + + for (tsx = 0; tsx < time_sig.length(); tsx++) { + if (time_sig[tsx].beat <= beat) { + // round m up to an integer (but allow for a small + // numerical inaccuracy) + m = m + (long) (0.99 + (time_sig[tsx].beat - prev_beat) / bpm); + bpm = time_sig[tsx].num * 4 / time_sig[tsx].den; + prev_beat = time_sig[tsx].beat; + prev_num = time_sig[tsx].num; + prev_den = time_sig[tsx].den; + } else { + m = m + (beat - prev_beat) / bpm; + *measure = (long) m; + *m_beat = (m - *measure) * bpm; + *num = prev_num; + *den = prev_den; + return; + } + } + // if we didn't return yet, compute after last time signature + Alg_time_sig initial(0, 4, 4); + Alg_time_sig &prev = initial; + if (tsx > 0) { // use last time signature + prev = time_sig[time_sig.length() - 1]; + } + bpm = prev.num * 4 / prev.den; + m = m + (beat - prev.beat) / bpm; + *measure = (long) m; + *m_beat = (m - *measure) * bpm; + *num = prev.num; + *den = prev.den; +} + +/* +void Alg_seq::set_events(Alg_event_ptr *events, long len, long max) +{ + convert_to_seconds(); // because notes are in seconds + notes.set_events(events, len, max); +} +*/ + + +void Alg_seq::iteration_begin() +{ + // keep an array of indexes into tracks + current = new long[track_list.length()]; + int i; + for (i = 0; i < track_list.length(); i++) { + current[i] = 0; + } +} + + +Alg_event_ptr Alg_seq::iteration_next() + // return the next event in time from any track +{ + long cur; // a track index + // find lowest next time of any track: + double next = 1000000.0; + int i, track; + for (i = 0; i < track_list.length(); i++) { + Alg_track &tr = track_list[i]; + cur = current[i]; + if (cur < tr.length() && tr[cur]->time < next) { + next = tr[cur]->time; + track = i; + } + } + if (next < 1000000.0) { + return track_list[track][current[track]++]; + } else { + return NULL; + } +} + + +void Alg_seq::iteration_end() +{ + delete[] current; +} + + +void Alg_seq::merge_tracks() +{ + long sum = 0; + long i; + for (i = 0; i < track_list.length(); i++) { + sum = sum + track(i)->length(); + } + // preallocate array for efficiency: + Alg_event_ptr *notes = new Alg_event_ptr[sum]; + iteration_begin(); + long notes_index = 0; + + Alg_event_ptr event; + while (event = iteration_next()) { + notes[notes_index++] = event; + } + track_list.reset(); // don't need them any more + add_track(0); + track(0)->set_events(notes, sum, sum); + iteration_end(); +} + + +// sr_letter_to_type = {"i": 'Integer', "r": 'Real', "s": 'String', +// "l": 'Logical', "a": 'Symbol'} + + diff --git a/plugins/midi_import/portsmf/allegro.h b/plugins/midi_import/portsmf/allegro.h new file mode 100644 index 000000000..ab5f6ff1f --- /dev/null +++ b/plugins/midi_import/portsmf/allegro.h @@ -0,0 +1,935 @@ +// Portsmf (also known as Allegro): +// music representation system, with +// extensible in-memory sequence structure +// upward compatible with MIDI +// implementations in C++ and Serpent +// external, text-based representation +// compatible with Aura +// +// SERIALBUFFER CLASS +// +// The Serial_buffer class is defined to support serialization and +// unserialization. A Serial_buffer is just a block of memory with +// a length and a read/write pointer. When writing, it can expand. +// +// SERIALIZATION +// +// The Alg_track class has static members: +// ser_buf -- a Serial_buffer +// When objects are serialized, they are first written to +// ser_buf, which is expanded whenever necessary. Then, when +// the length is known, new memory is allocated and the data +// is copied to a correctly-sized buffer and returned to caller. +// The "external" (callable from outside the library) +// serialization functions are: +// Alg_track::serialize() +// Alg_seq::serialize() +// The "internal" serialization functions to be called from within +// the library are: +// Alg_track::serialize_track(bool text) +// Alg_seq::serialize_seq(bool text) +// Alg_track::serialize_parameter( +// Alg_parameter *parm, bool text) +// These internal serialize functions append data to ser_buf The text +// flag says to write an ascii representation as opposed to binary. +// +// UNSERIALIZATION: +// +// The Alg_track class has a static member: +// unserialize(char *buffer, long len) +// that will unserialize anything -- an Alg_track or an Alg_seq. +// No other function should be called from outside the library. +// Internal unserialize functions are: +// Alg_seq::unserialize_seq() +// Alg_track::unserialize_track() +// Alg_track::unserialize_parameter(Alg_parameter_ptr parm_ptr) +// Just as serialization uses ser_buf for output, unserialization uses +// unser_buf for reading. unser_buf is another static member of Alg_track. + +#ifndef __ALLEGRO__ +#define __ALLEGRO__ +#include + +#define ALG_EPS 0.000001 // epsilon +#define ALG_DEFAULT_BPM 100.0 // default tempo + +// are d1 and d2 within epsilon of each other? +bool within(double d1, double d2, double epsilon); + +char *heapify(const char *s); // put a string on the heap + + +// Alg_attribute is an atom in the symbol table +// with the special addition that the last +// character is prefixed to the string; thus, +// the attribute 'tempor' (a real) is stored +// as 'rtempor'. To get the string name, just +// use attribute+1. +typedef char *Alg_attribute; +#define alg_attr_name(a) ((a) + 1) +#define alg_attr_type(a) (*(a)) + +// Alg_atoms is a symbol table of Alg_attributes and other +// unique strings +class Alg_atoms { +public: + Alg_atoms() { + maxlen = len = 0; + atoms = NULL; + } + // insert/lookup an atttribute + Alg_attribute insert_attribute(Alg_attribute attr); + // insert/lookup attribute by name (without prefixed type) + Alg_attribute insert_string(const char *name); +private: + long maxlen; + long len; + char **atoms; + + // insert an Attriubute not in table after moving attr to heap + Alg_attribute insert_new(const char *name, char attr_type); + void expand(); // make more space +}; + +extern Alg_atoms symbol_table; + + +// an attribute/value pair. Since Alg_attribute names imply type, +// we try to keep attributes and values packaged together as +// Alg_parameter class +typedef class Alg_parameter { +public: + ~Alg_parameter(); + Alg_attribute attr; + union { + double r;// real + char *s; // string + long i; // integer + bool l; // logical + char *a; // symbol (atom) + }; // anonymous union + void copy(Alg_parameter *); // copy from another parameter + char attr_type() { return alg_attr_type(attr); } + char *attr_name() { return alg_attr_name(attr); } + void set_attr(Alg_attribute a) { attr = a; } + void show(); +} *Alg_parameter_ptr; + + +// a list of attribute/value pairs +typedef class Alg_parameters { +public: + class Alg_parameters *next; + Alg_parameter parm; + + Alg_parameters(Alg_parameters *list) { + next = list; + } + + //~Alg_parameters() { } + + // each of these routines takes address of pointer to the list + // insertion is performed without checking whether or not a + // parameter already exists with this attribute. See find() and + // remove_key() to assist in checking for and removing existing + // parameters. + // Note also that these insert_* methods convert name to an + // attribute. If you have already done the symbol table lookup/insert + // you can do these operations faster (in which case we should add + // another set of functions that take attributes as arguments.) + static void insert_real(Alg_parameters **list, char *name, double r); + // insert string will copy string to heap + static void insert_string(Alg_parameters **list, char *name, char *s); + static void insert_integer(Alg_parameters **list, char *name, long i); + static void insert_logical(Alg_parameters **list, char *name, bool l); + static void insert_atom(Alg_parameters **list, char *name, char *s); + static Alg_parameters *remove_key(Alg_parameters **list, char *name); + // find an attribute/value pair + Alg_parameter_ptr find(Alg_attribute *attr); +} *Alg_parameters_ptr; + + +// these are type codes associated with certain attributes +// see Alg_track::find() where these are bit positions in event_type_mask +#define ALG_NOTE 0 // This is a note, not an update +#define ALG_GATE 1 // "gate" +#define ALG_BEND 2 // "bend" +#define ALG_CONTROL 3 // "control" +#define ALG_PROGRAM 4 // "program" +#define ALG_PRESSURE 5 // "pressure" +#define ALG_KEYSIG 6 // "keysig" +#define ALG_TIMESIG_NUM 7 // "timesig_num" +#define ALG_TIMESIG_DEN 8 // "timesig_den" +#define ALG_OTHER 9 // any other value + +// abstract superclass of Alg_note and Alg_update: +typedef class Alg_event { +protected: + bool selected; + char type; // 'e' event, 'n' note, 'u' update + long key; // note identifier + static const char* description; // static buffer for debugging (in Alg_event) +public: + double time; + long chan; + virtual void show() = 0; + // Note: there is no Alg_event() because Alg_event is an abstract class. + bool is_note() { return (type == 'n'); } // tell whether an Alg_event is a note + bool is_update() { return (type == 'u'); } // tell whether an Alg_event is a parameter update + char get_type() { return type; } // return 'n' for note, 'u' for update + int get_type_code(); // 1 = volume change, 2 = pitch bend, + // 3 = control change, 4 = program change, + // 5 = pressure change, 6 = key signature, + // 7 = time sig numerator, 8 = time sig denominator + bool get_selected() { return selected; } + void set_selected(bool b) { selected = b; } + // Note: notes are identified by a (channel, identifier) pair. + // For midi, the identifier is the key number (pitch). The identifier + // does not have to represent pitch; it's main purpose is to identify + // notes so that they can be named by subsequent update events. + long get_identifier() { return key; } // get MIDI key or note identifier of note or update + void set_identifier(long i) { key = i; } // set the identifier + // In all of these set_ methods, strings are owned by the caller and + // copied as necessary by the callee. For notes, an attribute/value + // pair is added to the parameters list. For updates, the single + // attribute/value parameter pair is overwritten. In all cases, the + // attribute (first argument) must agree in type with the second arg. + // The last letter of the attribute implies the type (see below). + void set_parameter(Alg_parameter_ptr new_parameter); + void set_string_value(char *attr, char *value); + void set_real_value(char *attr, double value); + void set_logical_value(char *attr, bool value); + void set_integer_value(char *attr, long value); + void set_atom_value(char *attr, char *atom); + + // Some note methods. These fail (via assert()) if this is not a note: + // + float get_pitch();// get pitch in steps -- use this even for MIDI + float get_loud(); // get loudness (MIDI velocity) + // times are in seconds or beats, depending upon the units_are_seconds + // flag in the containing sequence + double get_start_time(); // get start time in seconds or beats + double get_end_time(); // get end time in seconds or beats + double get_duration(); // get duration in seconds or beats + void set_pitch(float); + void set_loud(float); + void set_duration(double); + + // Notes have lists of attribute values. Attributes are converted + // to/from strings in this API to avoid explicit use of Alg_attribute + // types. Attribute names end with a type designation: 's', 'r', 'l', + // 'i', or 'a'. + // + bool has_attribute(char *attr); // test if note has attribute/value pair + char get_attribute_type(char *attr); // get the associated type: + // 's' = string, + // 'r' = real (double), 'l' = logical (bool), 'i' = integer (long), + // 'a' = atom (char *), a unique string stored in Alg_seq + char *get_string_value(char *attr, char *value = NULL); // get the string value + double get_real_value(char *attr, double value = 0.0); // get the real value + bool get_logical_value(char *attr, bool value = false); // get the logical value + long get_integer_value(char *attr, long value = 0); // get the integer value + char *get_atom_value(char *attr, char *value = NULL); // get the atom value + void delete_attribute(char *attr); // delete an attribute/value pair + // (ignore if no matching attribute/value pair exists) + + // Some attribute/value methods. These fail if this is not an update. + // Attributes are converted to/from strings to avoid explicit use + // of Alg_attribute types. + // + const char *get_attribute(); // get the update's attribute (string) + char get_update_type(); // get the update's type: 's' = string, + // 'r' = real (double), 'l' = logical (bool), 'i' = integer (long), + // 'a' = atom (char *), a unique string stored in Alg_seq + char *get_string_value(); // get the update's string value + // Notes: Caller does not own the return value. Do not modify. + // Do not use after underlying Alg_seq is modified. + double get_real_value(); // get the update's real value + bool get_logical_value(); // get the update's logical value + long get_integer_value(); // get the update's integer value + char *get_atom_value(); // get the update's atom value + // Notes: Caller does not own the return value. Do not modify. + // The return value's lifetime is forever. + + // Auxiliary function to aid in editing tracks + // Returns true if the event overlaps the given region + bool overlap(double t, double len, bool all); + + const char *GetDescription(); // computes a text description of this event + // the result is in a static buffer, not thread-safe, just for debugging. + Alg_event() { selected = false; } + virtual ~Alg_event() {} +} *Alg_event_ptr; + + +typedef class Alg_note : public Alg_event { +public: + virtual ~Alg_note(); + Alg_note(Alg_note *); // copy constructor + float pitch; // pitch in semitones (69 = A440) + float loud; // dynamic corresponding to MIDI velocity + double dur; // duration in seconds (normally to release point) + Alg_parameters_ptr parameters; // attribute/value pair list + Alg_note() { type = 'n'; parameters = NULL; } + void show(); +} *Alg_note_ptr; + + +typedef class Alg_update : public Alg_event { +public: + virtual ~Alg_update() {}; + Alg_update(Alg_update *); // copy constructor + Alg_parameter parameter; // an update contains one attr/value pair + + + Alg_update() { type = 'u'; } + void show(); +} *Alg_update_ptr; + + +// a sequence of Alg_event objects +typedef class Alg_events { +private: + long maxlen; + void expand(); +protected: + long len; + Alg_event_ptr *events; // events is array of pointers +public: + // sometimes, it is nice to have the time of the last note-off. + // In the current implementation, + // this field is set by append to indicate the time of the + // last note-off in the current unit, so it should be correct after + // creating a new track and adding notes to it. It is *not* + // updated after uninsert(), so use it with care. + double last_note_off; + virtual int length() { return len; } + Alg_event_ptr &operator[](int i) { + assert(i >= 0 && i < len); + return events[i]; + } + Alg_events() { + maxlen = len = 0; + events = NULL; + last_note_off = 0; + } + // destructor deletes the events array, but not the + // events themselves + ~Alg_events(); + void set_events(Alg_event_ptr *e, long l, long m) { + if (events) delete [] events; + events = e; len = l; maxlen = m; } + // for use by Alg_track and Alg_seq + void insert(Alg_event_ptr event); + void append(Alg_event_ptr event); + Alg_event_ptr uninsert(long index); +} *Alg_events_ptr; + +class Alg_track; + +typedef class Alg_event_list : public Alg_events { +protected: + char type; // 'e' Alg_event_list, 't' Alg_track, 's' Alg_seq + static const char *last_error_message; + Alg_track *events_owner; // if this is an Alg_event_list, + // the events are owned by an Alg_track or an Alg_seq + static int sequences; // to keep track of sequence numbers + int sequence_number; // this sequence number is incremented + // whenever an edit is performed on an Alg_track or Alg_seq. + // When an Alg_event_list is created to contain pointers to + // a subset of an Alg_track or Alg_seq (the events_owner), + // the Alg_event_list gets a copy of the events_owner's + // sequence_number. If the events_owner is edited, the pointers + // in this Alg_event_list will become invalid. This is detected + // (for debugging) as differing sequence_numbers. + + // every event list, track, and seq has a duration. + // Usually the duration is set when the list is constructed, e.g. + // when you extract from 10 to 15 seconds, the duration is 5 secs. + // The duration does not tell you when is the last note-off. + // duration is recorded in both beats and seconds: + double beat_dur; + double real_dur; +public: + // the client should not create one of these, but these are + // returned from various track and seq operations. An + // Alg_event_list "knows" the Alg_track or Alg_seq that "owns" + // the events. All events in an Alg_event_list must belong + // to the same Alg_track or Alg_seq structure. + // When applied to an Alg_seq, events are enumerated track + // by track with increasing indices. This operation is not + // particularly fast on an Alg_seq. + virtual Alg_event_ptr &operator[](int i); + Alg_event_list() { sequence_number = 0; + beat_dur = 0.0; real_dur = 0.0; events_owner = NULL; type = 'e'; } + Alg_event_list(Alg_track *owner); + + char get_type() { return type; } + Alg_track *get_owner() { return events_owner; } + + // The destructor does not free events because they are owned + // by a track or seq structure. + virtual ~Alg_event_list(); + + // Returns the duration of the sequence in beats or seconds + double get_beat_dur() { return beat_dur; } + void set_beat_dur(double d) { beat_dur = d; } + double get_real_dur() { return real_dur; } + void set_real_dur(double d) { real_dur = d; } + + // Events are stored in time order, so when you change the time of + // an event, you must adjust the position. When you call set_start_time + // on an Alg_event_list, the Alg_event_list is not modified, but the + // Alg_track that "owns" the event is modified. If the owner is an + // Alg_seq, this may require searching the seq for the track containing + // the event. This will mean a logN search of every track in the seq + // (but if this turns out to be a problem, we can store each event's + // track owner in the Alg_event_list.) + virtual void set_start_time(Alg_event *event, double); + // get text description of run-time errors detected, clear error + const char *get_last_error_message() { return last_error_message; } + // Implementation hint: keep a sequence number on each Alg_track that is + // incremented anytime there is a structural change. (This behavior is + // inherited by Alg_seq as well.) Copy the sequence number to any + // Alg_event_list object when it is created. Whenever you access an + // Alg_event_list, using operator[], assert that the Alg_event_list sequence + // number matches the Alg_seq sequence number. This will guarantee that you + // do not try to retain pointers to events beyond the point where the events + // may no longer exist. +} *Alg_event_list_ptr, &Alg_event_list_ref; + + +// Alg_beat is used to contruct a tempo map +typedef class Alg_beat { +public: + Alg_beat(double t, double b) { + time = t; beat = b; } + Alg_beat() {}; + double time; + double beat; +} *Alg_beat_ptr; + + +// Alg_beats is a list of Alg_beat objects used in Alg_seq +typedef class Alg_beats { +private: + long maxlen; + void expand(); +public: + long len; + Alg_beat_ptr beats; + Alg_beat &operator[](int i) { + assert(i >= 0 && i < len); + return beats[i]; + } + Alg_beats() { + maxlen = len = 0; + beats = NULL; + expand(); + beats[0].time = 0; + beats[0].beat = 0; + len = 1; + } + ~Alg_beats() { + if (beats) delete[] beats; + } + void insert(long i, Alg_beat_ptr beat); +} *Alg_beats_ptr; + + +typedef class Alg_time_map { +private: + int refcount; +public: + Alg_beats beats; // array of Alg_beat + double last_tempo; + bool last_tempo_flag; + Alg_time_map() { + last_tempo = ALG_DEFAULT_BPM / 60.0; // note: this value ignored until + // last_tempo_flag is set; nevertheless, the default + // tempo is 100. + last_tempo_flag = true; + refcount = 0; + } + Alg_time_map(Alg_time_map *map); // copy constructor + long length() { return beats.len; } + void show(); + long locate_time(double time); + long locate_beat(double beat); + double beat_to_time(double beat); + double time_to_beat(double time); + // Time map manipulations: it is prefered to call the corresponding + // methods in Alg_seq. If you manipulate an Alg_time_map directly, + // you should take care to convert all tracks that use the time map + // to beats or seconds as appropriate: Normally if you insert a beat + // you want tracks to be in time units and if you insert a tempo change + // you want tracks to be in beat units. + void insert_beat(double time, double beat); // add a point to the map + bool insert_tempo(double tempo, double beat); // insert a tempo change + // set the tempo over a region + bool set_tempo(double tempo, double start_beat, double end_beat); + void cut(double start, double len, bool units_are_seconds); + void trim(double start, double end, bool units_are_seconds); + void paste(double start, Alg_track *tr); + // insert a span of time. If start is at a tempo change, then + // the span of time runs at the changed tempo + void insert_time(double start, double len); + // insert a span of beats. If start is at a tempo change, the + // tempo change takes effect before the inserted beats + void insert_beats(double start, double len); + void dereference() { + if (--refcount <= 0) delete this; + } + void reference() { + refcount++; + } +} *Alg_time_map_ptr; + + +typedef class Serial_buffer { +private: + char *buffer; + char *ptr; + long len; +public: + Serial_buffer() { + buffer = NULL; + ptr = NULL; + len = 0; + } + void init_for_write() { ptr = buffer; } + long get_posn() { return (long) (ptr - buffer); } + long get_len() { return len; } + // store_long writes a long at a given offset + void store_long(long offset, long value) { + assert(offset <= get_posn() - 4); + long *loc = (long *) (buffer + offset); + *loc = value; + } + void check_buffer(long needed); + void set_string(char *s) { + char *fence = buffer + len; + assert(ptr < fence); + while ((*ptr++ = *s++)) assert(ptr < fence); + assert((char *)(((long) (ptr + 7)) & ~7) <= fence); + pad(); } + void set_int32(long v) { *((long *) ptr) = v; ptr += 4; } + void set_double(double v) { *((double *) ptr) = v; ptr += 8; } + void set_float(float v) { *((float *) ptr) = v; ptr += 4; } + void set_char(char v) { *ptr++ = v; } + void pad() { while (((long) ptr) & 7) set_char(0); } + void *to_heap(long *len) { + *len = get_posn(); + char *newbuf = new char[*len]; + memcpy(newbuf, buffer, *len); + return newbuf; + } + void init_for_read(void *buf, long n) { + buffer = (char *) buf; + ptr = (char *) buf; + len = n; + } + char get_char() { return *ptr++; } + long get_int32() { long i = *((long *) ptr); ptr += 4; return i; } + float get_float() { float f = *((float *) ptr); ptr += 4; return f; } + double get_double() { double d = *((double *) ptr); ptr += sizeof(double); + return d; } + char *get_string() { char *s = ptr; char *fence = buffer + len; + assert(ptr < fence); + while (*ptr++) assert(ptr < fence); + get_pad(); + return s; } + void get_pad() { while (((long) ptr) & 7) ptr++; } + void check_input_buffer(long needed) { + assert(get_posn() + needed <= len); } +} *Serial_buffer_ptr; + +typedef class Alg_seq *Alg_seq_ptr; + +typedef class Alg_track : public Alg_event_list { +protected: + Alg_time_map *time_map; + bool units_are_seconds; + char *get_string(char **p, long *b); + long get_int32(char **p, long *b); + double get_double(char **p, long *b); + float get_float(char **p, long *b); + static Serial_buffer ser_buf; + void serialize_parameter(Alg_parameter *parm); + // *buffer_ptr points to binary data, bytes_ptr points to how many + // bytes have been used so far, len is length of binary data + void unserialize_parameter(Alg_parameter_ptr parm_ptr); +public: + void serialize_track(); + void unserialize_track(); + virtual Alg_event_ptr &operator[](int i) { + assert(i >= 0 && i < len); + return events[i]; + } + Alg_track() { units_are_seconds = false; time_map = NULL; + set_time_map(NULL); type = 't'; } + // initialize empty track with a time map + Alg_track(Alg_time_map *map, bool seconds); + + Alg_event_ptr copy_event(Alg_event_ptr event); // make a complete copy + + Alg_track(Alg_track &track); // copy constructor, does not copy time_map + // copy constructor: event_list is copied, map is installed and referenced + Alg_track(Alg_event_list_ref event_list, Alg_time_map_ptr map, + bool units_are_seconds); + virtual ~Alg_track() { set_time_map(NULL); } + + // Returns a buffer containing a serialization of the + // file. It will be an ASCII representation unless text is true. + // *buffer gets a newly allocated buffer pointer. The caller must free it. + // *len gets the length of the serialized track + virtual void serialize(void **buffer, long *bytes); + + // Try to read from a memory buffer. Automatically guess + // whether it's MIDI or text. + static Alg_track *unserialize(void *buffer, long len); + + // If the track is really an Alg_seq and you need to access an + // Alg_seq method, coerce to an Alg_seq with this function: + Alg_seq_ptr to_alg_seq() { + return (get_type() == 's' ? (Alg_seq_ptr) this : NULL); } + + // Are we using beats or seconds? + bool get_units_are_seconds() { return units_are_seconds; } + // Change units + virtual void convert_to_beats(); + virtual void convert_to_seconds(); + void set_dur(double dur); + double get_dur() { return (units_are_seconds ? real_dur : beat_dur); } + + // Every Alg_track may have an associated time_map. If no map is + // specified, or if you set_time_map(NULL), then the behavior + // should be as if there is a constant tempo of 100 beats/minute + // (this constant is determined by ALG_DEFAULT_BPM). + // Recommendation: create a static global tempo map object. When + // any operation that needs a tempo map gets NULL, use the global + // tempo map. (Exception: any operation that would modify the + // tempo map should raise an error -- you don't want to change the + // default tempo map.) + virtual void set_time_map(Alg_time_map *map); + Alg_time_map *get_time_map() { return time_map; } + + // Methods to create events. The returned event is owned by the caller. + // Use delete to get rid of it unless you call add() -- see below. + // + Alg_note *create_note(double time, int channel, int identifier, + float pitch, float loudness, double duration); + // Note: after create_update(), caller should use set_*_value() to + // initialize the attribute/value pair: + Alg_update *create_update(double time, int channel, int identifier); + // Adds a new event - it is automatically inserted into the + // correct order in the sequence based on its timestamp. + // The ownership passes from the caller to this Alg_seq. The + // event is not copied. + virtual void add(Alg_event *event) { insert(event); } + + // + // Editing regions + // + + // Deletes the notes that start within the given region + // and returns them in a new sequence. The start times + // of the notes in the returned sequence are shifted + // by -t. The notes after the region get shifted over + // to fill the gap. In an Alg_seq, the tempo track is edited + // in a similar way + // and the cut tempo information is retained in the new seq. + // ONLY NOTES THAT START WITHIN THE REGION ARE CUT unless + // "all" is true in which case all notes that intersect + // the region are copied. CUT NOTES + // MAY EXTEND BEYOND THE DURATION OF THE RESULTING SEQ. + // The return type is the same as this (may be Alg_seq). + // All times including len are interpreted according to + // units_are_seconds in the track. + virtual Alg_track *cut(double t, double len, bool all); + + // Like cut() but doesn't remove the notes from the original + // sequence. The Alg_events are copied, not shared. ONLY EVENTS + // THAT START WITHIN THE REGION ARE COPIED unless "all" is true + // in which case all notes that intersect the region are + // copied. COPIED NOTES MAY + // EXTEND BEYOND THE DURATION OF THE RESULTING SEQ. + // The return type is the same as this (may be Alg_seq). + virtual Alg_track *copy(double t, double len, bool all); + + // Inserts a sequence in the middle, shifting some notes + // over by the duration of the seq, which is first converted + // to the same units (seconds or beats) as this. (This makes + // a differece because the pasted data may change the tempo, + // and notes that overlap the borders will then experience + // a tempo change.) + // THE SEQ PARAMETER IS NOT MODIFIED, AND Alg_event's ARE + // COPIED, NOT SHARED. + // The type of seq must be Alg_seq if seq is an Alg_seq, or + // Alg_track if seq is an Alg_track or an Alg_event_list. + virtual void paste(double t, Alg_event_list *seq); // Shifts notes + + // Merges two sequences with a certain offset. The offset is + // interpreted as either beats or seconds according to the + // current units of this, and seq is converted to the same + // units as this. Except for a possible conversion to beats + // or seconds, the tempo track of seq (if any) is ignored. + // (There is no way to merge tempo tracks.) + // THE SEQ PARAMETER IS NOT MODIFIED, AND Alg_event's ARE + // COPIED, NOT SHARED. + // The type of seq must be Alg_seq if seq is an Alg_seq, or + // Alg_track if seq is an Alg_track or an Alg_event_list. + virtual void merge(double t, Alg_event_list_ptr seq); + + // Deletes and shifts notes to fill the gap. The tempo track + // is also modified accordingly. ONLY EVENTS THAT START WITHIN + // THE REGION ARE DELETED unless "all" is true, in which case + // all notes that intersect the region are cleared. + // NOTES THAT EXTEND FROM BEFORE THE + // REGION INTO THE REGION RETAIN THEIR DURATION IN EITHER + // BEATS OR SECONDS ACCORDING TO THE CURRENT UNITS OF this. + virtual void clear(double t, double len, bool all); + + // Deletes notes but doesn't shift. If the "all" argument + // is true, deletes all notes that intersect the range at all, + // not just those that start within it. The tempo track is + // not affected. + virtual void silence(double t, double len, bool all); + + // Simply shifts notes past time t over by len, which is given + // in either beats or seconds according to the units of this. + // The resulting interveal (t, t+len) may in fact contain notes + // that begin before t. The durations of notes are not changed. + // If this is an Alg_seq, the tempo track is expanded at t also. + virtual void insert_silence(double t, double len); + + // + // Accessing for screen display + // + + // A useful generic function to retrieve only certain + // types of events. The masks should be bit-masks defined + // somewhere else. Part of the mask allows us to search for + // selected events. If this is an Alg_seq, search all tracks + // (otherwise, call track[i].find()) + // If channel_mask == 0, accept ALL channels + virtual Alg_event_list *find(double t, double len, bool all, + long channel_mask, long event_type_mask); + + // + // MIDI playback + // + // See Alg_iterator +} *Alg_track_ptr, &Alg_track_ref; + + +// Alg_time_sig represents a single time signature; +// although not recommended, time_signatures may have arbitrary +// floating point values, e.g. 4.5 beats per measure +typedef class Alg_time_sig { +public: + double beat; // when does this take effect? + double num; // what is the "numerator" (top number?) + double den; // what is the "denominator" (bottom number?) + Alg_time_sig(double b, double n, double d) { + beat = b; num = n; den = d; + } + Alg_time_sig() { + beat = 0; num = 0; den = 0; + } + void beat_to_measure(double beat, double *measure, double *m_beat, + double *num, double *den); + +} *Alg_time_sig_ptr; + + +// Alg_time_sigs is a dynamic array of time signatures +// +// The default (empty) time_sigs has 4/4 time at beat 0. +// Each time_sig object in time_sigs represents the beginning +// of a measure. If there is a beat missing, e.g. in the first +// measure, you can represent this by inserting another +// time_sig at the next measure beginning. Each time_sig implies +// an infinite sequence of full measures until the next time_sig. +// If you insert a time_sig and one already exist near the same +// beat, the old one is replaced, thus re-barring every measure +// until the next time_sig. +class Alg_time_sigs { +private: + long maxlen; + void expand(); // make more space + long len; + Alg_time_sig_ptr time_sigs; +public: + Alg_time_sigs() { + maxlen = len = 0; + time_sigs = NULL; + } + Alg_time_sig &operator[](int i) { // fetch a time signature + assert(i >= 0 && i < len); + return time_sigs[i]; + } + ~Alg_time_sigs() { + if (time_sigs) delete[] time_sigs; + } + void show(); + long length() { return len; } + int find_beat(double beat); + void insert(double beat, double num, double den); + void cut(double start, double end); // remove from start to end + void trim(double start, double end); // retain just start to end + void paste(double start, Alg_seq *seq); + void insert_beats(double beat, double len); // insert len beats at beat +}; + + +// a sequence of Alg_events objects +typedef class Alg_tracks { +private: + long maxlen; + void expand(); + void expand_to(int new_max); + long len; +public: + Alg_track_ptr *tracks; // tracks is array of pointers + Alg_track &operator[](int i) { + assert(i >= 0 && i < len); + return *tracks[i]; + } + long length() { return len; } + Alg_tracks() { + maxlen = len = 0; + tracks = NULL; + } + ~Alg_tracks(); + // Append a track to tracks. This Alg_tracks becomes the owner of track. + void append(Alg_track_ptr track); + void add_track(int track_num, Alg_time_map_ptr time_map, bool seconds); + void reset(); +} *Alg_tracks_ptr; + + +typedef enum { + alg_no_error = 0, // no error reading Allegro or MIDI file + alg_error_open = -800, // could not open Allegro or MIDI file + alg_error_syntax // something found in the file that could not be parsed; + // generally you should ignore syntax errors or look at the printed error messages + // because there are some things in standard midi files that we do not handle; + // (maybe we should only set alg_error_syntax when there is a real problem with + // the file as opposed to when there is some warning message for the user) +} Alg_error; + + +// An Alg_seq is an array of Alg_events, each a sequence of Alg_event, +// with a tempo map and a sequence of time signatures +// +typedef class Alg_seq : public Alg_track { +protected: + long *current; // array of indexes used by iteration methods + void serialize_seq(); + Alg_error error; // error code set by file readers + // an internal function used for writing Allegro track names + Alg_event_ptr write_track_name(std::ostream &file, int n, + Alg_events &events); +public: + int channel_offset_per_track; // used to encode track_num into channel + Alg_tracks track_list; // array of Alg_events + Alg_time_sigs time_sig; + int beat_x; + void basic_initialization() { + error = alg_no_error; + units_are_seconds = true; type = 's'; + channel_offset_per_track = 0; + add_track(0); // default is one empty track + } + Alg_seq() { + basic_initialization(); + } + // copy constructor -- if track is an Alg_seq, make a copy; if + // track is just an Alg_track, the track becomes track 0 + Alg_seq(Alg_track_ref track) { seq_from_track(track); } + Alg_seq(Alg_track_ptr track) { seq_from_track(*track); } + void seq_from_track(Alg_track_ref tr); + Alg_seq(std::istream &file, bool smf); // create from file + Alg_seq(const char *filename, bool smf); // create from filename + ~Alg_seq(); + int get_read_error() { return error; } + void serialize(void **buffer, long *bytes); + void copy_time_sigs_to(Alg_seq *dest); // a utility function + void set_time_map(Alg_time_map *map); + + // encode sequence structure into contiguous, moveable memory block + // address of newly allocated memory is assigned to *buffer, which must + // be freed by caller; the length of data is assigned to *len + void unserialize_seq(); + + // write an ascii representation to file + void write(std::ostream &file, bool in_secs); + // returns true on success + bool write(const char *filename); + void smf_write(std::ofstream &file); + bool smf_write(const char *filename); + + // Returns the number of tracks + int tracks(); + + // create a track + void add_track(int track_num) { + track_list.add_track(track_num, get_time_map(), units_are_seconds); + } + + // Return a particular track. This Alg_seq owns the track, so the + // caller must not delete the result. + Alg_track_ptr track(int); + + virtual Alg_event_ptr &operator[](int i); + + virtual void convert_to_seconds(); + virtual void convert_to_beats(); + + Alg_track_ptr cut_from_track(int track_num, double start, double dur, + bool all); + Alg_seq *cut(double t, double len, bool all); + void insert_silence_in_track(int track_num, double t, double len); + void insert_silence(double t, double len); + Alg_track_ptr copy_track(int track_num, double t, double len, bool all); + Alg_seq *copy(double start, double len, bool all); + void paste(double start, Alg_seq *seq); + virtual void clear(double t, double len, bool all); + virtual void merge(double t, Alg_event_list_ptr seq); + virtual void silence(double t, double len, bool all); + void clear_track(int track_num, double start, double len, bool all); + void silence_track(int track_num, double start, double len, bool all); + Alg_event_list_ptr find_in_track(int track_num, double t, double len, + bool all, long channel_mask, + long event_type_mask); + + // find index of first score event after time + long seek_time(double time, int track_num); + bool insert_beat(double time, double beat); + // warning: insert_tempo may change representation from seconds to beats + bool insert_tempo(double bpm, double beat); + + // add_event takes a pointer to an event on the heap. The event is not + // copied, and this Alg_seq becomes the owner and freer of the event. + void add_event(Alg_event_ptr event, int track_num); + void add(Alg_event_ptr event) { assert(false); } // call add_event instead + // warning: set_tempo may change representation from seconds to beats + bool set_tempo(double bpm, double start_beat, double end_beat); + void set_time_sig(double beat, double num, double den); + void beat_to_measure(double beat, long *measure, double *m_beat, + double *num, double *den); + // void set_events(Alg_event_ptr *events, long len, long max); + void merge_tracks(); // move all track data into one track + void iteration_begin(); // prepare to enumerate events in order + Alg_event_ptr iteration_next(); // return next event (or NULL) + void iteration_end(); // clean up after enumerating events +} *Alg_seq_ptr, &Alg_seq_ref; + + +// see Alg_seq::Alg_seq() constructors that read from files +// the following are for internal library implementation and are +// moved to *_internal.h header files. +//Alg_seq_ptr alg_read(std::istream &file, Alg_seq_ptr new_seq); +//Alg_seq_ptr alg_smf_read(std::istream &file, Alg_seq_ptr new_seq); +#endif diff --git a/plugins/midi_import/portsmf/allegrord.cpp b/plugins/midi_import/portsmf/allegrord.cpp new file mode 100644 index 000000000..a6c17c057 --- /dev/null +++ b/plugins/midi_import/portsmf/allegrord.cpp @@ -0,0 +1,755 @@ +#include "assert.h" +#include "stdlib.h" +#include "string.h" +#include "ctype.h" +#include "trace.h" +#include +#include +#include +#include "strparse.h" +#include "allegro.h" +#include "algrd_internal.h" + +using namespace std; + +#define streql(s1, s2) (strcmp(s1, s2) == 0) +#define field_max 80 + +class Alg_reader { +public: + istream *file; + string input_line; + int line_no; + String_parse line_parser; + bool line_parser_flag; + string field; + bool error_flag; + Alg_seq_ptr seq; + double tsnum; + double tsden; + + Alg_reader(istream *a_file, Alg_seq_ptr new_seq); + void readline(); + Alg_parameters_ptr process_attributes(Alg_parameters_ptr attributes, + double time); + bool parse(); + long parse_chan(string &field); + long parse_int(string &field); + int find_real_in(string &field, int n); + double parse_real(string &field); + void parse_error(string &field, long offset, char *message); + double parse_dur(string &field, double base); + double parse_after_dur(double dur, string &field, int n, double base); + double parse_loud(string &field); + long parse_key(string &field); + double parse_pitch(string &field); + long parse_after_key(int key, string &field, int n); + long find_int_in(string &field, int n); + bool parse_attribute(string &field, Alg_parameter_ptr parm); + bool parse_val(Alg_parameter_ptr param, string &s, int i); + bool check_type(char type_char, Alg_parameter_ptr param); +}; + + +double Alg_reader::parse_pitch(string &field) +{ + if (isdigit(field[1])) { + int last = find_real_in(field, 1); + string real_string = field.substr(1, last - 1); + return atof(real_string.c_str()); + } else { + return (double) parse_key(field); + } +} + + +// it is the responsibility of the caller to delete +// the seq +Alg_reader::Alg_reader(istream *a_file, Alg_seq_ptr new_seq) +{ + file = a_file; // save the file + line_parser_flag = false; + line_no = 0; + tsnum = 4; // default time signature + tsden = 4; + seq = new_seq; +} + + +Alg_error alg_read(istream &file, Alg_seq_ptr new_seq) + // read a sequence from allegro file +{ + assert(new_seq); + Alg_reader alg_reader(&file, new_seq); + bool err = alg_reader.parse(); + return (err ? alg_error_syntax : alg_no_error); +} + + +void Alg_reader::readline() +{ + // a word about memory management: this Alg_reader has a + // member variable input_line that holds a line of input + // it is reused for each line. input_line is parsed by + // line_parser, which holds a reference to input_line + line_parser_flag = false; + if (getline(*file, input_line)) { + line_parser.init(&input_line); + line_parser_flag = true; + error_flag = false; + } +} + + +Alg_parameters_ptr Alg_reader::process_attributes( + Alg_parameters_ptr attributes, double time) +{ + // print "process_attributes:", attributes + bool ts_flag = false; + if (attributes) { + Alg_parameters_ptr a; + bool in_seconds = seq->get_units_are_seconds(); + if (a = Alg_parameters::remove_key(&attributes, "tempor")) { + double tempo = a->parm.r; + seq->insert_tempo(tempo, seq->get_time_map()->time_to_beat(time)); + } + if (a = Alg_parameters::remove_key(&attributes, "beatr")) { + double beat = a->parm.r; + seq->insert_beat(time, beat); + } + if (a = Alg_parameters::remove_key(&attributes, "timesig_numr")) { + tsnum = a->parm.r; + ts_flag = true; + } + if (a = Alg_parameters::remove_key(&attributes, "timesig_denr")) { + tsden = a->parm.r; + ts_flag = true; + } + if (ts_flag) { + seq->set_time_sig(seq->get_time_map()->time_to_beat(time), + tsnum, tsden); + } + if (in_seconds) seq->convert_to_seconds(); + } + return attributes; // in case it was modified +} + + +bool Alg_reader::parse() +{ + int voice = 0; + int key = 60; + double loud = 100.0; + double pitch = 60.0; + double dur = 1.0; + double time = 0.0; + int track_num = 0; + seq->convert_to_seconds(); + //seq->set_real_dur(0.0); // just in case it's not initialized already + readline(); + bool valid = false; // ignore blank lines + while (line_parser_flag) { + bool time_flag = false; + bool next_flag = false; + double next; + bool voice_flag = false; + bool loud_flag = false; + bool dur_flag = false; + bool new_pitch_flag = false; // "P" syntax or "A"-"G" syntax + double new_pitch = 0.0; + bool new_key_flag = false; // "K" syntax + int new_key = 0; + Alg_parameters_ptr attributes = NULL; + if (line_parser.peek() == '#') { + // look for #track + line_parser.get_nonspace_quoted(field); + if (streql(field.c_str(), "#track")) { + line_parser.get_nonspace_quoted(field); // number + field.insert(0, " "); // need char at beginning because + // parse_int ignores the first character of the argument + track_num = parse_int(field); + seq->add_track(track_num); + } + // maybe we have a sequence or track name + line_parser.get_remainder(field); + // if there is a non-space character after #track n then + // use it as sequence or track name. Note that because we + // skip over spaces, a sequence or track name cannot begin + // with leading blanks. Another decision is that the name + // must be at time zero + if (field.length() > 0) { + // insert the field as sequence name or track name + Alg_update_ptr update = new Alg_update; + update->chan = -1; + update->time = 0; + update->set_identifier(-1); + // sequence name is whatever is on track 0 + // other tracks have track names + const char *attr = + (track_num == 0 ? "seqnames" : "tracknames"); + update->parameter.set_attr(symbol_table.insert_string(attr)); + update->parameter.s = heapify(field.c_str()); + seq->add_event(update, track_num); + } + } else { + // we must have a track to insert into + if (seq->tracks() == 0) seq->add_track(0); + line_parser.get_nonspace_quoted(field); + char pk = line_parser.peek(); + // attributes are parsed as two adjacent nonspace_quoted tokens + // so we have to conditionally call get_nonspace_quoted() again + if (pk && !isspace(pk)) { + string field2; + line_parser.get_nonspace_quoted(field2); + field.append(field2); + } + while (field[0]) { + char first = toupper(field[0]); + if (strchr("ABCDEFGKLPUSIQHW-", first)) { + valid = true; // it's a note or event + } + if (first == 'V') { + if (voice_flag) { + parse_error(field, 0, "Voice specified twice"); + } else { + voice = parse_chan(field); + } + voice_flag = true; + } else if (first == 'T') { + if (time_flag) { + parse_error(field, 0, "Time specified twice"); + } else { + time = parse_dur(field, 0.0); + } + time_flag = true; + } else if (first == 'N') { + if (next_flag) { + parse_error(field, 0, "Next specified twice"); + } else { + next = parse_dur(field, time); + } + next_flag = true; + } else if (first == 'K') { + if (new_key_flag) { + parse_error(field, 0, "Key specified twice"); + } else { + new_key = parse_key(field); + new_key_flag = true; + } + } else if (first == 'L') { + if (loud_flag) { + parse_error(field, 0, "Loudness specified twice"); + } else { + loud = parse_loud(field); + } + loud_flag = true; + } else if (first == 'P') { + if (new_pitch_flag) { + parse_error(field, 0, "Pitch specified twice"); + } else { + new_pitch = parse_pitch(field); + new_pitch_flag = true; + } + } else if (first == 'U') { + if (dur_flag) { + parse_error(field, 0, "Dur specified twice"); + } else { + dur = parse_dur(field, time); + dur_flag = true; + } + } else if (strchr("SIQHW", first)) { + if (dur_flag) { + parse_error(field, 0, "Dur specified twice"); + } else { + // prepend 'U' to field, copy EOS too + field.insert(0, 1, 'U'); + dur = parse_dur(field, time); + dur_flag = true; + } + } else if (strchr("ABCDEFG", first)) { + if (new_pitch_flag) { + parse_error(field, 0, "Pitch specified twice"); + } else { + // prepend 'P' to field + field.insert(0, 1, 'P'); + new_pitch = parse_pitch(field); + new_pitch_flag = true; + } + } else if (first == '-') { + Alg_parameter parm; + if (parse_attribute(field, &parm)) { // enter attribute-value pair + attributes = new Alg_parameters(attributes); + attributes->parm = parm; + parm.s = NULL; // protect string from deletion by destructor + } + } else { + parse_error(field, 0, "Unknown field"); + } + + if (error_flag) { + field[0] = 0; // exit the loop + } else { + line_parser.get_nonspace_quoted(field); + pk = line_parser.peek(); + // attributes are parsed as two adjacent nonspace_quoted + // tokens so we have to conditionally call + // get_nonspace_quoted() again + if (pk && !isspace(pk)) { + string field2; + line_parser.get_nonspace_quoted(field2); + field.append(field2); + } + } + } + // a case analysis: + // Key < 128 implies pitch unless pitch is explicitly given + // Pitch implies Key unless key is explicitly given, + // Pitch is rounded to nearest integer to determine the Key + // if necessary, so MIDI files will lose the pitch fraction + // A-G is a Pitch specification (therefore it implies Key) + // K60 P60 -- both are specified, use 'em + // K60 P60 C4 -- overconstrained, an error + // K60 C4 -- OK, but K60 is already implied by C4 + // K60 -- OK, pitch is 60 + // C4 P60 -- over constrained + // P60 -- OK, key is 60 + // P60.1 -- OK, key is 60 + // C4 -- OK, key is 60, pitch is 60 + // -- OK, key and pitch from before + // K200 P60 -- ok, pitch is 60 + // K200 with neither P60 nor C4 uses + // pitch from before + + // figure out what the key/instance is: + if (new_key_flag) { // it was directly specified + key = new_key; + } else if (new_pitch_flag) { + // pitch was specified, but key was not; get key from pitch + key = (int) (new_pitch + 0.5); // round to integer key number + } + if (new_pitch_flag) { + pitch = new_pitch; + } else if (key < 128 && new_key_flag) { + // no explicit pitch, but key < 128, so it implies pitch + pitch = key; + new_pitch_flag = true; + } + // now we've acquired new parameters + // if (it is a note, then enter the note + if (valid) { + // change tempo or beat + attributes = process_attributes(attributes, time); + // if there's a duration or pitch, make a note: + if (new_pitch_flag || dur_flag) { + Alg_note_ptr note_ptr = new Alg_note; + note_ptr->chan = voice; + note_ptr->time = time; + note_ptr->dur = dur; + note_ptr->set_identifier(key); + note_ptr->pitch = pitch; + note_ptr->loud = loud; + note_ptr->parameters = attributes; + seq->add_event(note_ptr, track_num); // sort later + if (seq->get_real_dur() < (time + dur)) seq->set_real_dur(time + dur); + } else { + int update_key = -1; + // key must appear explicitly; otherwise + // update applies to channel + if (new_key_flag) { + update_key = key; + } + if (loud_flag) { + Alg_update_ptr new_upd = new Alg_update; + new_upd->chan = voice; + new_upd->time = time; + new_upd->set_identifier(update_key); + new_upd->parameter.set_attr(symbol_table.insert_string("loudr")); + new_upd->parameter.r = pitch; + seq->add_event(new_upd, track_num); + if (seq->get_real_dur() < time) seq->set_real_dur(time); + } + if (attributes) { + while (attributes) { + Alg_update_ptr new_upd = new Alg_update; + new_upd->chan = voice; + new_upd->time = time; + new_upd->set_identifier(update_key); + new_upd->parameter = attributes->parm; + seq->add_event(new_upd, track_num); + Alg_parameters_ptr p = attributes; + attributes = attributes->next; + p->parm.s = NULL; // so we don't delete the string + delete p; + } + } + } + if (next_flag) { + time = time + next; + } else if (dur_flag || new_pitch_flag) { // a note: incr by dur + time = time + dur; + } + } + } + readline(); + } + if (!error_flag) { // why not convert even if there was an error? -RBD + seq->convert_to_seconds(); // make sure format is correct + } + // real_dur is valid, translate to beat_dur + seq->set_beat_dur((seq->get_time_map())->time_to_beat(seq->get_real_dur())); + return error_flag; +} + + +long Alg_reader::parse_chan(string &field) +{ + const char *int_string = field.c_str() + 1; + char *msg = "Integer or - expected"; + const char *p = int_string; + char c; + // check that all chars in int_string are digits or '-': + while (c = *p++) { + if (!isdigit(c) && c != '-') { + parse_error(field, p - field.c_str() - 1, msg); + return 0; + } + } + p--; // p now points to end-of-string character + if (p - int_string == 0) { + // bad: string length is zero + parse_error(field, 1, msg); + return 0; + } + if (p - int_string == 1 && int_string[0] == '-') { + // special case: entire string is "-", interpret as -1 + return -1; + } + return atoi(int_string); +} + + +long Alg_reader::parse_int(string &field) +{ + const char *int_string = field.c_str() + 1; + char *msg = "Integer expected"; + const char *p = int_string; + char c; + // check that all chars in int_string are digits: + while (c = *p++) { + if (!isdigit(c)) { + parse_error(field, p - field.c_str() - 1, msg); + return 0; + } + } + p--; // p now points to end-of-string character + if (p - int_string == 0) { + // bad: string length is zero + parse_error(field, 1, msg); + return 0; + } + return atoi(int_string); +} + + +int Alg_reader::find_real_in(string &field, int n) +{ + // scans from offset n to the end of a real constant + bool decimal = false; + int len = field.length(); + for (int i = n; i < len; i++) { + char c = field[i]; + if (!isdigit(c)) { + if (c == '.' && !decimal) { + decimal = true; + } else { + return i; + } + } + } + return field.length(); +} + + +double Alg_reader::parse_real(string &field) +{ + char *msg = "Real expected"; + int last = find_real_in(field, 1); + string real_string = field.substr(1, last - 1); + if (last <= 1 || last < (int) field.length()) { + parse_error(field, 1, msg); + return 0; + } + return atof(real_string.c_str()); +} + + +void Alg_reader::parse_error(string &field, long offset, char *message) +{ + int position = line_parser.pos - field.length() + offset; + error_flag = true; + puts(line_parser.str->c_str()); + for (int i = 0; i < position; i++) { + putc(' ', stdout); + } + putc('^', stdout); + printf(" %s\n", message); +} + + +double duration_lookup[] = { 0.25, 0.5, 1.0, 2.0, 4.0 }; + + +double Alg_reader::parse_dur(string &field, double base) +{ + char *msg = "Duration expected"; + char *durs = "SIQHW"; + char *p; + int last; + double dur; + if (field.length() < 2) { + // fall through to error message + return -1; + } else if (isdigit(field[1])) { + last = find_real_in(field, 1); + string real_string = field.substr(1, last - 1); + dur = atof(real_string.c_str()); + // convert dur from seconds to beats + dur = seq->get_time_map()->time_to_beat(base + dur) - + seq->get_time_map()->time_to_beat(base); + } else if (p = strchr(durs, toupper(field[1]))) { + dur = duration_lookup[p - durs]; + last = 2; + } else { + parse_error(field, 1, msg); + return 0; + } + dur = parse_after_dur(dur, field, last, base); + dur = seq->get_time_map()->beat_to_time( + seq->get_time_map()->time_to_beat(base) + dur) - base; + return dur; +} + + +double Alg_reader::parse_after_dur(double dur, string &field, + int n, double base) +{ + if ((int) field.length() == n) { + return dur; + } + if (toupper(field[n]) == 'T') { + return parse_after_dur(dur * 2/3, field, n + 1, base); + } + if (field[n] == '.') { + return parse_after_dur(dur * 1.5, field, n + 1, base); + } + if (isdigit(field[n])) { + int last = find_real_in(field, n); + string a_string = field.substr(n, last - n); + double f = atof(a_string.c_str()); + return parse_after_dur(dur * f, field, last, base); + } + if (field[n] == '+') { + string a_string = field.substr(n + 1); + return dur + parse_dur( + a_string, seq->get_time_map()->beat_to_time( + seq->get_time_map()->time_to_beat(base) + dur)); + } + parse_error(field, n, "Unexpected character in duration"); + return dur; +} + +struct loud_lookup_struct { + char *str; + int val; +} loud_lookup[] = { {"FFF", 127}, {"FF", 120}, {"F", 110}, {"MF", 100}, + {"MP", 90}, {"P", 80}, {"PP", 70}, {"PPP", 60}, + {NULL, 0} }; + + +double Alg_reader::parse_loud(string &field) +{ + char *msg = "Loudness expected"; + if (isdigit(field[1])) { + return parse_int(field); + } else { + string dyn = field.substr(1); + transform(dyn.begin(), dyn.end(), dyn.begin(), ::toupper); + for (int i = 0; loud_lookup[i].str; i++) { + if (streql(loud_lookup[i].str, dyn.c_str())) { + return (double) loud_lookup[i].val; + } + } + } + parse_error(field, 1, msg); + return 100.0; +} + + +int key_lookup[] = {21, 23, 12, 14, 16, 17, 19}; + + +// the field can be K or K[A-G] or P[A-G] +// (this can be called from parse_pitch() to handle [A-G]) +// Notice that the routine ignores the first character: K or P +// +long Alg_reader::parse_key(string &field) +{ + char *msg = "Pitch expected"; + char *pitches = "ABCDEFG"; + char *p; + if (isdigit(field[1])) { + // This routine would not have been called if field = "P" + // so it must be "K" so must be an integer. + return parse_int(field); + } else if (p = strchr(pitches, toupper(field[1]))) { + long key = key_lookup[p - pitches]; + key = parse_after_key(key, field, 2); + return key; + } + parse_error(field, 1, msg); + return 0; +} + + +long Alg_reader::parse_after_key(int key, string &field, int n) +{ + if ((int) field.length() == n) { + return key; + } + char c = toupper(field[n]); + if (c == 'S') { + return parse_after_key(key + 1, field, n + 1); + } + if (c == 'F') { + return parse_after_key(key - 1, field, n + 1); + } + if (isdigit(field[n])) { + int last = find_int_in(field, n); + string octave = field.substr(n, last - n); + int oct = atoi(octave.c_str()); + return parse_after_key(key + oct * 12, field, last); + } + parse_error(field, n, "Unexpected character in pitch"); + return key; +} + + +long Alg_reader::find_int_in(string &field, int n) +{ + while ((int) field.length() > n && isdigit(field[n])) { + n = n + 1; + } + return n; +} + + +bool Alg_reader::parse_attribute(string &field, Alg_parameter_ptr param) +{ + int i = 1; + while (i < (int) field.length()) { + if (field[i] == ':') { + string attr = field.substr(1, i - 1); + char type_char = field[i - 1]; + if (strchr("iarsl", type_char)) { + param->set_attr(symbol_table.insert_string(attr.c_str())); + parse_val(param, field, i + 1); + } else { + parse_error(field, 0, "attribute needs to end with typecode: i,a,r,s, or l"); + } + return !error_flag; + } + i = i + 1; + } + return false; +} + + +bool Alg_reader::parse_val(Alg_parameter_ptr param, string &s, int i) +{ + int len = (int) s.length(); + if (i >= len) { + return false; + } + if (s[i] == '"') { + if (!check_type('s', param)) { + return false; + } + // note: (len - i) includes 2 quote characters but no EOS character + // so total memory to allocate is (len - i) - 1 + char *r = new char[(len - i) - 1]; + strncpy(r, s.c_str() + i + 1, (len - i) - 2); + r[(len - i) - 2] = 0; // terminate the string + param->s = r; + } else if (s[i] == '\'') { + if (!check_type('a', param)) { + return false; + } + string r = s.substr(i + 1, len - i - 2); + param->a = symbol_table.insert_string(r.c_str()); + } else if (param->attr_type() == 'l') { + if (streql(s.c_str() + i, "true") || + streql(s.c_str() + i, "t")) { + param->l = true; + } else if (streql(s.c_str() + i, "false") || + streql(s.c_str() + i, "nil")) { + param->l = false; + } else return false; + } else if (isdigit(s[i]) || s[i] == '-' || s[i] == '.') { + int pos = i; + bool period = false; + int sign = 1; + if (s[pos] == '-') { + sign = -1; + pos++; + } + while (pos < len) { + if (isdigit(s[pos])) { + ; + } else if (!period && s[pos] == '.') { + period = true; + } else { + parse_error(s, pos, "Unexpected char in number"); + return false; + } + pos = pos + 1; + } + string r = s.substr(i, len - i); + if (period) { + if (!check_type('r', param)) { + return false; + } + param->r = atof(r.c_str()); + } else { + if (param->attr_type() == 'r') { + param->r = atoi(r.c_str()); + } else if (!check_type('i', param)) { + return false; + } else { + param->i = atoi(r.c_str()); + } + } + } else { + parse_error(s, i, "invalid value"); + return false; + } + return true; +} + + +bool Alg_reader::check_type(char type_char, Alg_parameter_ptr param) +{ + return param->attr_type() == type_char; +} + + +//duration_lookup = {"S": 0.5, "I": 0.5, "Q": 1, "H": 2, "W": 4} +//key_lookup = {"C": 12, "D": 14, "E": 16, "F": 17, "G": 19, "A": 21, "B": 23} + +/* +def test(): + reader = Alg_reader(open("data\\test.gro", "r")) + reader.parse() + score = reader->seq.notes + print "score:", score + reader = nil +*/ diff --git a/plugins/midi_import/portsmf/allegroserial.cpp b/plugins/midi_import/portsmf/allegroserial.cpp new file mode 100644 index 000000000..e6b52f339 --- /dev/null +++ b/plugins/midi_import/portsmf/allegroserial.cpp @@ -0,0 +1,2 @@ +// allegroserial.cpp -- convert track to memory buffer and back to structure + diff --git a/plugins/midi_import/portsmf/allegrosmfrd.cpp b/plugins/midi_import/portsmf/allegrosmfrd.cpp new file mode 100644 index 000000000..257cb947d --- /dev/null +++ b/plugins/midi_import/portsmf/allegrosmfrd.cpp @@ -0,0 +1,445 @@ +// midifile reader + +#include "stdlib.h" +#include "stdio.h" +#include "string.h" +#include "assert.h" +#include +#include +#include "allegro.h" +#include "algsmfrd_internal.h" +#include "mfmidi.h" +#include "trace.h" + +using namespace std; + +typedef class Alg_pending { +public: + Alg_note_ptr note; + class Alg_pending *next; + Alg_pending(Alg_note_ptr n, class Alg_pending *list) { + note = n; next = list; } +} *Alg_pending_ptr; + + +class Alg_midifile_reader: public Midifile_reader { +public: + istream *file; + Alg_seq_ptr seq; + int divisions; + Alg_pending_ptr pending; + Alg_track_ptr track; + int track_number; // the number of the (current) track + // chan is actual_channel + channel_offset_per_track * track_num + + // channel_offset_per_track * port + long channel_offset_per_track; // used to encode track number into channel + // default is 0, set this to 0 to merge all tracks to 16 channels + long channel_offset_per_port; // used to encode port number into channel + // default is 16, set to 0 to ignore port prefix meta events + // while reading, this is channel_offset_per_track * track_num + int channel_offset; + + Alg_midifile_reader(istream &f, Alg_seq_ptr new_seq) { + file = &f; + pending = NULL; + seq = new_seq; + channel_offset_per_track = 0; + channel_offset_per_port = 16; + track_number = -1; // no tracks started yet, 1st will be #0 + meta_channel = -1; + port = 0; + } + // delete destroys the seq member as well, so set it to NULL if you + // copied the pointer elsewhere + ~Alg_midifile_reader(); + // the following is used to load the Alg_seq from the file: + bool parse(); + + void set_nomerge(bool flag) { Mf_nomerge = flag; } + void set_skipinit(bool flag) { Mf_skipinit = flag; } + long get_currtime() { return Mf_currtime; } + +protected: + int meta_channel; // the channel for meta events, set by MIDI chan prefix + int port; // value from the portprefix meta event + + double get_time(); + void update(int chan, int key, Alg_parameter_ptr param); + void *Mf_malloc(size_t size) { return malloc(size); } + void Mf_free(void *obj, size_t size) { free(obj); } + /* Methods to be called while processing the MIDI file. */ + void Mf_starttrack(); + void Mf_endtrack(); + int Mf_getc(); + void Mf_chanprefix(int chan); + void Mf_portprefix(int port); + void Mf_eot(); + void Mf_error(char *); + void Mf_header(int,int,int); + void Mf_on(int,int,int); + void Mf_off(int,int,int); + void Mf_pressure(int,int,int); + void Mf_controller(int,int,int); + void Mf_pitchbend(int,int,int); + void Mf_program(int,int); + void Mf_chanpressure(int,int); + void binary_msg(int len, char *msg, const char *attr_string); + void Mf_sysex(int,char*); + void Mf_arbitrary(int,char*); + void Mf_metamisc(int,int,char*); + void Mf_seqnum(int); + void Mf_smpte(int,int,int,int,int); + void Mf_timesig(int,int,int,int); + void Mf_tempo(int); + void Mf_keysig(int,int); + void Mf_sqspecific(int,char*); + void Mf_text(int,int,char*); +}; + + +Alg_midifile_reader::~Alg_midifile_reader() +{ + while (pending) { + Alg_pending_ptr to_be_freed = pending; + pending = pending->next; + delete to_be_freed; + } + finalize(); // free Mf reader memory +} + + +bool Alg_midifile_reader::parse() +{ + channel_offset = 0; + seq->convert_to_beats(); + midifile(); + seq->set_real_dur(seq->get_time_map()->beat_to_time(seq->get_beat_dur())); + return midifile_error != 0; +} + + +void Alg_midifile_reader::Mf_starttrack() +{ + // printf("starting new track\n"); + // create a new track that will share the sequence time map + // since time is in beats, the seconds parameter is false + track_number++; + seq->add_track(track_number); // make sure track exists + track = seq->track(track_number); // keep pointer to current track + meta_channel = -1; + port = 0; +} + + +void Alg_midifile_reader::Mf_endtrack() +{ + // note: track is already part of seq, so do not add it here + // printf("finished track, length %d number %d\n", track->len, track_num / 100); + channel_offset += seq->channel_offset_per_track; + track = NULL; + double now = get_time(); + if (seq->get_beat_dur() < now) seq->set_beat_dur(now); + meta_channel = -1; + port = 0; +} + + +int Alg_midifile_reader::Mf_getc() +{ + return file->get(); +} + + +void Alg_midifile_reader::Mf_chanprefix(int chan) +{ + meta_channel = chan; +} + + +void Alg_midifile_reader::Mf_portprefix(int p) +{ + port = p; +} + + +void Alg_midifile_reader::Mf_eot() +{ + meta_channel = -1; + port = 0; +} + + +void Alg_midifile_reader::Mf_error(char *msg) +{ + fprintf(stdout, "Midifile reader error: %s\n", msg); +} + + +void Alg_midifile_reader::Mf_header(int format, int ntrks, int division) +{ + if (format > 1) { + char msg[80]; + sprintf(msg, "file format %d not implemented", format); + Mf_error(msg); + } + divisions = division; +} + + +double Alg_midifile_reader::get_time() +{ + double beat = ((double) get_currtime()) / divisions; + return beat; +} + + +void Alg_midifile_reader::Mf_on(int chan, int key, int vel) +{ + assert(!seq->get_units_are_seconds()); + if (vel == 0) { + Mf_off(chan, key, vel); + return; + } + Alg_note_ptr note = new Alg_note(); + pending = new Alg_pending(note, pending); + /* trace("on: %d at %g\n", key, get_time()); */ + note->time = get_time(); + note->chan = chan + channel_offset + port * channel_offset_per_port; + note->dur = 0; + note->set_identifier(key); + note->pitch = (float) key; + note->loud = (float) vel; + track->append(note); + meta_channel = -1; +} + + +void Alg_midifile_reader::Mf_off(int chan, int key, int vel) +{ + double time = get_time(); + Alg_pending_ptr *p = &pending; + while (*p) { + if ((*p)->note->get_identifier() == key && + (*p)->note->chan == + chan + channel_offset + port * channel_offset_per_port) { + (*p)->note->dur = time - (*p)->note->time; + // trace("updated %d dur %g\n", (*p)->note->key, (*p)->note->dur); + Alg_pending_ptr to_be_freed = *p; + *p = to_be_freed->next; + delete to_be_freed; + } else { + p = &((*p)->next); + } + } + meta_channel = -1; +} + + +void Alg_midifile_reader::update(int chan, int key, Alg_parameter_ptr param) +{ + Alg_update_ptr update = new Alg_update; + update->time = get_time(); + update->chan = chan; + if (chan != -1) { + update->chan = chan + channel_offset + port * channel_offset_per_port; + } + update->set_identifier(key); + update->parameter = *param; + // prevent the destructor from destroying the string twice! + // the new Update takes the string from param + if (param->attr_type() == 's') param->s = NULL; + track->append(update); +} + + +void Alg_midifile_reader::Mf_pressure(int chan, int key, int val) +{ + Alg_parameter parameter; + parameter.set_attr(symbol_table.insert_string("pressurer")); + parameter.r = val / 127.0; + update(chan, key, ¶meter); + meta_channel = -1; +} + + +void Alg_midifile_reader::Mf_controller(int chan, int control, int val) +{ + Alg_parameter parameter; + char name[32]; + sprintf(name, "control%dr", control); + parameter.set_attr(symbol_table.insert_string(name)); + parameter.r = val / 127.0; + update(chan, -1, ¶meter); + meta_channel = -1; +} + + +void Alg_midifile_reader::Mf_pitchbend(int chan, int c1, int c2) +{ + Alg_parameter parameter; + parameter.set_attr(symbol_table.insert_string("bendr")); + parameter.r = ((c2 << 7) + c1) / 8192.0 - 1.0; + update(chan, -1, ¶meter); + meta_channel = -1; +} + + +void Alg_midifile_reader::Mf_program(int chan, int program) +{ + Alg_parameter parameter; + parameter.set_attr(symbol_table.insert_string("programi")); + parameter.i = program; + update(chan, -1, ¶meter); + meta_channel = -1; +} + + +void Alg_midifile_reader::Mf_chanpressure(int chan, int val) +{ + Alg_parameter parameter; + parameter.set_attr(symbol_table.insert_string("pressurer")); + parameter.r = val / 127.0; + update(chan, -1, ¶meter); + meta_channel = -1; +} + + +void Alg_midifile_reader::binary_msg(int len, char *msg, + const char *attr_string) +{ + Alg_parameter parameter; + char *hexstr = new char[len * 2 + 1]; + for (int i = 0; i < len; i++) { + sprintf(hexstr + 2 * i, "%02x", (0xFF & msg[i])); + } + parameter.s = hexstr; + parameter.set_attr(symbol_table.insert_string(attr_string)); + update(meta_channel, -1, ¶meter); +} + + +void Alg_midifile_reader::Mf_sysex(int len, char *msg) +{ + // sysex messages become updates with attribute sysexs and a hex string + binary_msg(len, msg, "sysexs"); +} + + +void Alg_midifile_reader::Mf_arbitrary(int len, char *msg) +{ + Mf_error("arbitrary data ignored"); +} + + +void Alg_midifile_reader::Mf_metamisc(int type, int len, char *msg) +{ + char text[128]; + sprintf(text, "metamsic data, type 0x%x, ignored", type); + Mf_error(text); +} + + +void Alg_midifile_reader::Mf_seqnum(int n) +{ + Mf_error("seqnum data ignored"); +} + + +static char *fpsstr[4] = {"24", "25", "29.97", "30"}; + +void Alg_midifile_reader::Mf_smpte(int hours, int mins, int secs, + int frames, int subframes) +{ + // string will look like "24fps:01h:27m:07s:19.00f" + // 30fps (drop frame) is notated as "29.97fps" + char text[32]; + int fps = (hours >> 6) & 3; + hours &= 0x1F; + sprintf(text, "%sfps:%02dh:%02dm:%02ds:%02d.%02df", + fpsstr[fps], hours, mins, secs, frames, subframes); + Alg_parameter smpteoffset; + smpteoffset.s = heapify(text); + smpteoffset.set_attr(symbol_table.insert_string("smpteoffsets")); + update(meta_channel, -1, &smpteoffset); + // Mf_error("SMPTE data ignored"); +} + + +void Alg_midifile_reader::Mf_timesig(int i1, int i2, int i3, int i4) +{ + seq->set_time_sig(get_currtime() / divisions, i1, 1 << i2); +} + + +void Alg_midifile_reader::Mf_tempo(int tempo) +{ + double beat = get_currtime(); + beat = beat / divisions; // convert to quarters + // 6000000 us/min / n us/beat => beat / min + double bpm = 60000000.0 / tempo; + seq->insert_tempo(bpm, beat); +} + + +void Alg_midifile_reader::Mf_keysig(int key, int mode) +{ + Alg_parameter key_parm; + key_parm.set_attr(symbol_table.insert_string("keysigi")); + // use 0 for C major, 1 for G, -1 for F, etc., that is, + // the number of sharps, where flats are negative sharps + key_parm.i = key; //<<<---- fix this + // use -1 to mean "all channels" + update(meta_channel, -1, &key_parm); + Alg_parameter mode_parm; + mode_parm.set_attr(symbol_table.insert_string("modea")); + mode_parm.a = (mode == 0 ? symbol_table.insert_string("major") : + symbol_table.insert_string("minor")); + update(meta_channel, -1, &mode_parm); +} + + +void Alg_midifile_reader::Mf_sqspecific(int len, char *msg) +{ + // sequencer specific messages become updates with attribute sqspecifics + // and a hex string for the value + binary_msg(len, msg, "sqspecifics"); +} + + +char *heapify2(int len, char *s) +{ + char *h = new char[len + 1]; + memcpy(h, s, len); + h[len] = 0; + return h; +} + + +void Alg_midifile_reader::Mf_text(int type, int len, char *msg) +{ + Alg_parameter text; + text.s = heapify2(len, msg); + const char *attr = "miscs"; + if (type == 1) attr = "texts"; + else if (type == 2) attr = "copyrights"; + else if (type == 3) + attr = (track_number == 0 ? "seqnames" : "tracknames"); + else if (type == 4) attr = "instruments"; + else if (type == 5) attr = "lyrics"; + else if (type == 6) attr = "markers"; + else if (type == 7) attr = "cues"; + text.set_attr(symbol_table.insert_string(attr)); + update(meta_channel, -1, &text); +} + + +// parse file into a seq. +Alg_error alg_smf_read(istream &file, Alg_seq_ptr new_seq) +{ + assert(new_seq); + Alg_midifile_reader ar(file, new_seq); + bool err = ar.parse(); + ar.seq->set_real_dur(ar.seq->get_time_map()-> + beat_to_time(ar.seq->get_beat_dur())); + return (err ? alg_error_syntax : alg_no_error); +} diff --git a/plugins/midi_import/portsmf/allegrosmfwr.cpp b/plugins/midi_import/portsmf/allegrosmfwr.cpp new file mode 100644 index 000000000..55bc485ca --- /dev/null +++ b/plugins/midi_import/portsmf/allegrosmfwr.cpp @@ -0,0 +1,649 @@ +// allegrosmfwr.cpp -- Allegro Standard Midi File Write + +#include +#include +#include +#include +#include +#include +#include +using namespace std; +#include "allegro.h" + +// event_queue is a list element that keeps track of pending +// things to write to a track, including note-ons, note-offs, +// updates, tempo changes, and time signatures +// +class event_queue{ +public: + char type;//'n' for note, 'o' for off, 's' for time signature, + // 'c' for tempo changes + double time; + long index; //of the event in mSeq->notes + class event_queue *next; + event_queue(char t, double when, long x, class event_queue *n) { + type = t; time = when; index = x; next = n; } +}; + + +class Alg_smf_write { +public: + Alg_smf_write(Alg_seq_ptr seq); + ~Alg_smf_write(); + long channels_per_track; // used to encode track number into chan field + // chan is actual_channel + channels_per_track * track_number + // default is 100, set this to 0 to merge all tracks to 16 channels + + void write(ofstream &file /* , midiFileFormat = 1 */); + +private: + long previous_divs; // time in ticks of most recently written event + + void write_track(int i); + void write_tempo(int divs, int tempo); + void write_tempo_change(int i); + void write_time_signature(int i); + void write_note(Alg_note_ptr note, bool on); + void write_update(Alg_update_ptr update); + void write_text(Alg_update_ptr update, char type); + void write_binary(int type_byte, char *msg); + void write_midi_channel_prefix(Alg_update_ptr update); + void write_smpteoffset(Alg_update_ptr update, char *s); + void write_data(int data); + int to_midi_channel(int channel); + int to_track(int channel); + + ostream *out_file; + + Alg_seq_ptr seq; + + int num_tracks; // number of tracks not counting tempo track + int division; // divisions per quarter note, default = 120 + int initial_tempo; + + int timesig_num; // numerator of time signature + int timesig_den; // denominator of time signature + double timesig_when; // time of time signature + + int keysig; // number of sharps (+) or flats (-), -99 for undefined + char keysig_mode; // 'M' or 'm' for major/minor + double keysig_when; // time of key signature + + void write_delta(double event_time); + void write_varinum(int num); + void write_16bit(int num); + void write_24bit(int num); + void write_32bit(int num); +}; + +#define ROUND(x) (int) ((x)+0.5) + +Alg_smf_write::Alg_smf_write(Alg_seq_ptr a_seq) +{ + out_file = NULL; + + // at 100bpm (a nominal tempo value), we would like a division + // to represent 1ms of time. So + // d ticks/beat * 100 beats/min = 60,000 ms/min * 1 tick/ms + // solving for d, d = 600 + division = 600; // divisions per quarter note + timesig_num = timesig_den = 0; // initially undefined + keysig = -99; + keysig_mode = 0; + initial_tempo = 500000; + + seq = a_seq; + + previous_divs = 0; // used to compute deltas for midifile +} + + +Alg_smf_write::~Alg_smf_write() +{ +} + + +// sorting is quite subtle due to rounding +// For example, suppose times from a MIDI file are exact, but in +// decimal round to TW0.4167 Q0.3333. Since the time in whole notes +// rounded up, this note will start late. Even though the duration +// rounded down, the amount is 1/4 as much because units are quarter +// notes. Therefore, the total roundup is 0.0001 beats. This is +// enough to cause the note to sort later in the queue, perhaps +// coming after a new note-on on the same pitch, and resulting in +// a turning on-off, on-off into on, on, off, off if data is moved +// to Allegro (ascii) format with rounding and then back to SMF. +// +// The solution here is to consider things that round to the same +// tick to be simultaneous. Then, be sure to deal with note-offs +// before note-ons. We're going to do that by using event_queue +// times that are rounded to the nearest tick time. Except note-offs +// are going to go in with times that are 1/4 tick earlier so they +// get scheduled first, but still end up on the same tick. +// +event_queue* push(event_queue *queue, event_queue *event) +{ + // printf("push: %.6g, %c, %d\n", event->time, event->type, event->index); + if (queue == NULL) { + event->next = NULL; + return event; + } + + event_queue *marker1 = NULL; + event_queue *marker2 = queue; + while (marker2 != NULL && marker2->time <= event->time) { + marker1 = marker2; + marker2 = marker2->next; + } + event->next = marker2; + if (marker1 != NULL) { + marker1->next=event; + return queue; + } else return event; +} + + +void print_queue(event_queue *q) +{ + printf("Printing queue. . .\n"); + event_queue *q2=q; + while (q2) { + printf("%c at %f ;", q2->type, q2->time); + q2 = q2->next; + } + printf("\nDone printing.\n"); +} + + +void Alg_smf_write::write_note(Alg_note_ptr note, bool on) +{ + double event_time = (on ? note->time : note->time + note->dur); + write_delta(event_time); + + //printf("deltaDivisions: %d, beats elapsed: %g, on? %c\n", deltaDivisions, note->time, on); + + char chan = (note->chan & 15); + int pitch = int(note->pitch + 0.5); + if (pitch < 0) { + pitch = pitch % 12; + } else if (pitch > 127) { + pitch = (pitch % 12) + 120; // put pitch in 10th octave + if (pitch > 127) pitch -= 12; // or 9th octave + } + out_file->put(0x90 + chan); + out_file->put(pitch); + if (on) { + int vel = (int) note->loud; + if (vel <= 0) vel = 1; + write_data(vel); + } else out_file->put(0); // note-off indicated by velocty zero +} + + +void Alg_smf_write::write_midi_channel_prefix(Alg_update_ptr update) +{ + if (update->chan >= 0) { // write MIDI Channel Prefix + write_delta(update->time); + out_file->put(0xFF); // Meta Event + out_file->put(0x20); // Type code for MIDI Channel Prefix + out_file->put(1); // length + out_file->put(to_midi_channel(update->chan)); + // one thing odd about the Std MIDI File spec is that once + // you turn on MIDI Channel Prefix, there seems to be no + // way to cancel it unless a non-Meta event shows up. We + // don't do any analysis to avoid assigning channels to + // meta events. + } +} + + +void Alg_smf_write::write_text(Alg_update_ptr update, char type) +{ + write_midi_channel_prefix(update); + write_delta(update->time); + out_file->put(0xFF); + out_file->put(type); + out_file->put((char) strlen(update->parameter.s)); + *out_file << update->parameter.s; +} + + +void Alg_smf_write::write_smpteoffset(Alg_update_ptr update, char *s) +{ + write_midi_channel_prefix(update); + write_delta(update->time); + out_file->put(0xFF); // meta event + out_file->put(0x54); // smpte offset type code + out_file->put(5); // length + for (int i = 0; i < 5; i++) *out_file << s[i]; +} + + +// write_data - limit data to the range of [0...127] and write it +void Alg_smf_write::write_data(int data) +{ + if (data < 0) data = 0; + else if (data > 0x7F) data = 0x7F; + + out_file->put(data); +} + + +int Alg_smf_write::to_midi_channel(int channel) +{ + // allegro track number is stored as multiple of 100 + // also mask off all but 4 channel bits just in case + if (channels_per_track > 0) channel %= channels_per_track; + return channel & 0xF; +} + + +int Alg_smf_write::to_track(int channel) +{ + if (channel == -1) return 0; + return channel / channels_per_track; +} + + +static char hex_to_nibble(char c) +{ + if (isalpha(c)) { + return 10 + (toupper(c) - 'A'); + } else { + return c - '0'; + } +} + + +static char hex_to_char(char *s) +{ + return (hex_to_nibble(s[0]) << 4) + hex_to_nibble(s[1]); +} + + +void Alg_smf_write::write_binary(int type_byte, char *msg) +{ + int len = strlen(msg) / 2; + out_file->put(type_byte); + write_varinum(len); + for (int i = 0; i < len; i++) { + out_file->put(hex_to_char(msg)); + msg += 2; + } +} + + +void Alg_smf_write::write_update(Alg_update_ptr update) +{ + char *name = update->parameter.attr_name(); + + /****Non-Meta Events****/ + if (!strcmp(name, "pressurer")) { + write_delta(update->time); + if (update->get_identifier() < 0) { // channel pressure message + out_file->put(0xD0 + to_midi_channel(update->chan)); + write_data((int)(update->parameter.r * 127)); + } else { // just 1 key -- poly pressure + out_file->put(0xA0 + to_midi_channel(update->chan)); + write_data(update->get_identifier()); + write_data((int)(update->parameter.r * 127)); + } + } else if (!strcmp(name, "programi")) { + write_delta(update->time); + out_file->put(0xC0 + to_midi_channel(update->chan)); + write_data(update->parameter.i); + } else if (!strcmp(name, "bendr")) { + int temp = ROUND(0x2000 * (update->parameter.r + 1)); + if (temp > 0x3fff) temp = 0x3fff; // 14 bits maximum + if (temp < 0) temp = 0; + int c1 = temp & 0x7F; // low 7 bits + int c2 = temp >> 7; // high 7 bits + write_delta(update->time); + out_file->put(0xE0 + to_midi_channel(update->chan)); + write_data(c1); + write_data(c2); + } else if (!strncmp(name, "control", 7) && + update->parameter.attr_type() == 'r') { + int ctrlnum = atoi(name + 7); + int val = ROUND(update->parameter.r * 127); + write_delta(update->time); + out_file->put(0xB0 + to_midi_channel(update->chan)); + write_data(ctrlnum); + write_data(val); + } else if (!strcmp(name, "sysexs") && + update->parameter.attr_type() == 's') { + char *s = update->parameter.s; + if (s[0] && s[1] && toupper(s[0]) == 'F' && s[1] == '0') { + s += 2; // skip the initial "F0" byte in message: it is implied + } + write_delta(update->time); + write_binary(0xF0, s); + } else if (!strcmp(name, "sqspecifics") && + update->parameter.attr_type() == 's') { + char *s = update->parameter.s; + write_delta(update->time); + out_file->put(0xFF); + write_binary(0x7F, s); + + /****Text Events****/ + } else if (!strcmp(name, "texts")) { + write_text(update, 0x01); + } else if (!strcmp(name, "copyrights")) { + write_text(update, 0x02); + } else if (!strcmp(name, "seqnames") || !strcmp(name, "tracknames")) { + write_text(update, 0x03); + } else if (!strcmp(name, "instruments")) { + write_text(update, 0x04); + } else if (!strcmp(name, "lyrics")) { + write_text(update, 0x05); + } else if (!strcmp(name, "markers")) { + write_text(update, 0x06); + } else if (!strcmp(name, "cues")) { + write_text(update, 0x07); + } else if (!strcmp(name, "miscs")) { + write_text(update, 0x08); + + /****Other Events****/ + } else if (!strcmp(name, "smpteoffsets")) { +#define decimal(p) (((p)[0] - '0') * 10 + ((p)[1] - '0')) + // smpteoffset is specified as "24fps:00h:10m:00s:11.00f" + // the following simple parser does not reject all badly + // formatted strings, but it should parse good strings ok + char *s = update->parameter.s; + int len = strlen(s); + char smpteoffset[5]; + if (len < 24) return; // not long enough, must be bad format + int fps; + if (s[0] == '2') { + if (s[1] == '4') fps = 0; + else if (s[1] == '5') fps = 1; + else if (s[1] == '9') { + fps = 2; + if (len != 27) return; // not right length + s += 3; // cancel effect of longer string + } + } else fps = 3; + s += 6; int hours = decimal(s); + s += 4; int mins = decimal(s); + s += 4; int secs = decimal(s); + s += 4; int frames = decimal(s); + s += 3; int subframes = decimal(s); + smpteoffset[0] = (fps << 6) + hours; + smpteoffset[1] = mins; + smpteoffset[2] = secs; + smpteoffset[3] = frames; + smpteoffset[4] = subframes; + write_smpteoffset(update, smpteoffset); + + // key signature is special because it takes two events in the Alg_seq + // structure to make one midi file event. When we encounter one or + // the other event, we'll just record it in the Alg_smf_write object. + // After both events are seen, we write the data. (See below.) + } else if (!strcmp(name, "keysigi")) { + keysig = update->parameter.i; + keysig_when = update->time; + } else if (!strcmp(name, "modea")) { + if (!strcmp(alg_attr_name(update->parameter.a), "major")) + keysig_mode = 'M'; + else keysig_mode = 'm'; + keysig_when = update->time; + } + if (keysig != -99 && keysig_mode) { // write when both are defined + write_delta(keysig_when); + out_file->put(0xFF); + out_file->put(0x59); + out_file->put(2); + // mask off high bits so that this value appears to be positive + // i.e. -1 -> 0xFF (otherwise, write_data will clip -1 to 0) + out_file->put(keysig & 0xFF); + out_file->put(keysig_mode == 'm'); + keysig = -99; + keysig_mode = false; + } + //printf("Update: %s, key: %g\n", update->parameter.attr_name(), update->key); +} + + +// see notes on event_queue::push, TICK_TIME converts from beat to +// the number of the nearest tick. The second parameter is an offset in +// quarter ticks. By scheduling with -1, note-offs should get dispatched +// first. Note that TICK_TIME only determines the order of events, so +// it is ok to change units from beats to ticks, saving a divide. +#define TICK_TIME(t, o) (ROUND((t) * division) + 0.25 * (o)) + +void Alg_smf_write::write_track(int i) +{ + int j = 0; // note index + Alg_events ¬es = seq->track_list[i]; + event_queue *pending = NULL; + if (notes.length() > 0) { + pending = new event_queue('n', TICK_TIME(notes[j]->time, 0), 0, NULL); + } + if (i == 0) { // track 0 may have tempo and timesig info + if (seq->get_time_map()->last_tempo_flag || seq->get_time_map()->beats.len > 0) { + pending = push(pending, new event_queue('c', 0.0, 0, NULL)); + } + if (seq->time_sig.length() > 0) { + pending = push(pending, new event_queue('s', + TICK_TIME(seq->time_sig[0].beat, 0), 0, NULL)); + } + } + while (pending) { + event_queue *current = pending; + pending = pending->next; + if (current->type == 'n') { + Alg_note_ptr n = (Alg_note_ptr) notes[current->index]; + if (n->is_note()) { + write_note(n, true); + pending = push(pending, new event_queue('o', + TICK_TIME(n->time + n->dur, -1), current->index, NULL)); + } else if (n->is_update()) { + Alg_update_ptr u = (Alg_update_ptr) n; + write_update(u); + } + int next = current->index + 1; + if (next < notes.length()) { + current->time = TICK_TIME(notes[next]->time, 0); + current->index = next; + pending = push(pending, current); + } + } else if (current->type == 'o') { //note-off + Alg_note_ptr n = (Alg_note_ptr) notes[current->index]; + write_note(n, false); + delete current; + } else if (current->type == 'c') { // tempo change + write_tempo_change(current->index); + current->index++; // -R + if (current->index < seq->get_time_map()->beats.len) { + current->time = + TICK_TIME(seq->get_time_map()-> + beats[current->index].beat, 0); + pending = push(pending, current); + } else { + delete current; + } + } else if (current->type == 's') { // time sig + write_time_signature(current->index); + current->index++; + if (current->index < seq->time_sig.length()) { + current->time = + TICK_TIME(seq->time_sig[current->index].beat, 0); + pending = push(pending, current); + } else { + delete current; + } + } + } +} + + +void Alg_smf_write::write_tempo(int divs, int tempo) +{ + // printf("Inserting tempo %f after %f clocks.\n", tempo, delta); + write_varinum(divs - previous_divs); + previous_divs = divs; + out_file->put(0xFF); + out_file->put(0x51); + out_file->put(0x03); + write_24bit((int)tempo); +} + + +void Alg_smf_write::write_tempo_change(int i) + // i is index of tempo map +{ + // extract tempo map + Alg_beats &b = seq->get_time_map()->beats; + double tempo; + long divs; + if (i < seq->get_time_map()->beats.len - 1) { + tempo = 1000000 * ((b[i+1].time - b[i].time) / + (b[i+1].beat - b[i].beat)); + divs = ROUND(b[i].beat * division); + write_tempo(divs, ROUND(tempo)); + } else if (seq->get_time_map()->last_tempo_flag) { // write the final tempo + divs = ROUND(division * b[i].beat); + tempo = (1000000.0 / seq->get_time_map()->last_tempo); + write_tempo(divs, ROUND(tempo)); + } +} + + +void Alg_smf_write::write_time_signature(int i) +{ + Alg_time_sigs &ts = seq->time_sig; + // write the time signature + long divs = ROUND(ts[i].beat * division); + write_varinum(divs - previous_divs); + out_file->put(0xFF); + out_file->put(0x58); // time signature + out_file->put(4); // length of message + out_file->put(ROUND(ts[i].num)); + int den = ROUND(ts[i].den); + int den_byte = 0; + while (den > 1) { // compute the log2 of denominator + den_byte++; + den >>= 1; + } + out_file->put(den_byte); + out_file->put(24); // clocks per quarter + out_file->put(8); // 32nd notes per 24 clocks +} + + + +void Alg_smf_write::write(ofstream &file) +{ + int track_len_offset; + int track_end_offset; + int track_len; + + out_file = &file; + + // Header + file << "MThd"; + + write_32bit(6); // chunk length + + write_16bit(1); // format 1 MIDI file + + write_16bit(seq->tracks()); // number of tracks + write_16bit(division); // divisions per quarter note + + + // write_ all tracks + seq->convert_to_beats(); + int i; + for (i = 0; i < seq->tracks(); i++) { + previous_divs = 0; + *out_file << "MTrk"; + track_len_offset = out_file->tellp(); + write_32bit(0); // track len placeholder + + write_track(i); + + // End of track event + write_varinum(0); // delta time + out_file->put(0xFF); + out_file->put(0x2F); + out_file->put(0x00); + + // Go back and write in the length of the track + track_end_offset = out_file->tellp(); + track_len = track_end_offset - track_len_offset - 4; + out_file->seekp(track_len_offset); + write_32bit(track_len); + out_file->seekp(track_end_offset); + } +} + + +void Alg_smf_write::write_16bit(int num) +{ + out_file->put((num & 0xFF00) >> 8); + out_file->put(num & 0xFF); +} + +void Alg_smf_write::write_24bit(int num) +{ + out_file->put((num & 0xFF0000) >> 16); + out_file->put((num & 0xFF00) >> 8); + out_file->put((num & 0xFF)); +} + +void Alg_smf_write::write_32bit(int num) +{ + out_file->put((num & 0xFF000000) >> 24); + out_file->put((num & 0xFF0000) >> 16); + out_file->put((num & 0xFF00) >> 8); + out_file->put((num & 0xFF)); +} + + +void Alg_smf_write::write_delta(double event_time) +{ + // divisions is ideal absolute time in divisions + long divisions = ROUND(division * event_time); + long delta_divs = divisions - previous_divs; + write_varinum(delta_divs); + previous_divs = divisions; +} + + +void Alg_smf_write::write_varinum(int value) +{ + if(value<0) value=0;//this line should not have to be here! + int buffer; + + buffer = value & 0x7f; + while ((value >>= 7) > 0) { + buffer <<= 8; + buffer |= 0x80; + buffer += (value & 0x7f); + } + + for(;;) { + out_file->put(buffer); + if (buffer & 0x80) + buffer >>= 8; + else + break; + } +} + + +void Alg_seq::smf_write(ofstream &file) +{ + Alg_smf_write writer(this); + writer.write(file); +} + +bool Alg_seq::smf_write(const char *filename) +{ + ofstream outf(filename, ios::binary | ios::out); + if (outf.fail()) return false; + smf_write(outf); + outf.close(); + return true; +} + diff --git a/plugins/midi_import/portsmf/allegrowr.cpp b/plugins/midi_import/portsmf/allegrowr.cpp new file mode 100644 index 000000000..f4a76f18a --- /dev/null +++ b/plugins/midi_import/portsmf/allegrowr.cpp @@ -0,0 +1,181 @@ +// allegrowr.cpp -- write sequence to an Allegro file (text) + +#include "assert.h" +#include "stdlib.h" +#include +#include +#include +#include +#include +#include "memory.h" +using namespace std; +#include "strparse.h" +#include "allegro.h" + +// Note about precision: %g prints 6 significant digits. For 1ms precision, +// the maximum magnitude is 999.999, i.e. 1000s < 17minutes. For anything +// over 1000s, time in seconds will be printed with 10ms precision, which +// is not good. Therefore, times and durations are printed as %.4d, which +// gives 100us precision. +// The following define allows you to change this decision: +/* #define TIMFMT "%.4d" */ +#define TIMPREC 4 +#define TIMFMT fixed << setprecision(TIMPREC) +#define GFMT resetiosflags(ios::floatfield) << setprecision(6) + +void parameter_print(ostream &file, Alg_parameter_ptr p) +{ + file << " -" << p->attr_name() << ":"; + switch (p->attr_type()) { + case 'a': + file << "'" << alg_attr_name(p->a) << "'"; + break; + case 'i': + file << p->i; + break; + case 'l': + file << (p->l ? "true" : "false"); + break; + case 'r': + file << p->r; + break; + case 's': { + string str; + string_escape(str, p->s, "\""); + file << str; + break; + } + } +} + +Alg_event_ptr Alg_seq::write_track_name(ostream &file, int n, + Alg_events &events) +// write #track +// if we write the name on the "#track" line, then we do *not* want +// to write again as an update: "-seqnames:"Jordu", so if we do +// find a name and write it, return a pointer to it so the track +// writer knows what update (if any) to skip +{ + Alg_event_ptr e = NULL; + file << "#track " << n; + const char *attr = symbol_table.insert_string( + n == 0 ? "seqnames" : "tracknames"); + // search for name in events with timestamp of 0 + for (int i = 0; i < events.length(); i++) { + e = events[i]; + if (e->time > 0) break; + if (e->is_update()) { + Alg_update_ptr u = (Alg_update_ptr) e; + if (u->parameter.attr == attr) { + file << " " << u->parameter.s; + break; + } + } + } + file << endl; + return e; +} + + +void Alg_seq::write(ostream &file, bool in_secs) +{ + int i, j; + if (in_secs) convert_to_seconds(); + else convert_to_beats(); + Alg_event_ptr update_to_skip = write_track_name(file, 0, track_list[0]); + Alg_beats &beats = time_map->beats; + for (i = 0; i < beats.len - 1; i++) { + Alg_beat_ptr b = &(beats[i]); + if (in_secs) { + file << "T" << TIMFMT << b->time; + } else { + file << "TW" << TIMFMT << b->beat / 4; + } + double tempo = (beats[i + 1].beat - b->beat) / + (beats[i + 1].time - beats[i].time); + file << " -tempor:" << GFMT << tempo * 60 << "\n"; + } + if (time_map->last_tempo_flag) { // we have final tempo: + Alg_beat_ptr b = &(beats[beats.len - 1]); + if (in_secs) { + file << "T" << TIMFMT << b->time; + } else { + file << "TW" << TIMFMT << b->beat / 4; + } + file << " -tempor:" << GFMT << time_map->last_tempo * 60.0 << "\n"; + } + + // write the time signatures + for (i = 0; i < time_sig.length(); i++) { + Alg_time_sig &ts = time_sig[i]; + double time = ts.beat; + if (in_secs) { + file << "T" << TIMFMT << time << " V- -timesig_numr:" << + GFMT << ts.num << "\n"; + file << "T" << TIMFMT << time << " V- -timesig_denr:" << + GFMT << ts.den << "\n"; + } else { + double wholes = ts.beat / 4; + file << "TW" << TIMFMT << wholes << " V- -timesig_numr:" << + GFMT << ts.num << "\n"; + file << "TW" << TIMFMT << wholes << " V- -timesig_denr:" << + GFMT << ts.den << "\n"; + } + } + + for (j = 0; j < track_list.length(); j++) { + Alg_events ¬es = track_list[j]; + if (j != 0) update_to_skip = write_track_name(file, j, notes); + // now write the notes at beat positions + for (i = 0; i < notes.length(); i++) { + Alg_event_ptr e = notes[i]; + // if we already wrote this event as a track or sequence name, + // do not write it again + if (e == update_to_skip) continue; + double start = e->time; + if (in_secs) { + file << "T" << TIMFMT << start; + } else { + file << "TW" << TIMFMT << start / 4; + } + // write the channel as Vn or V- + if (e->chan == -1) file << " V-"; + else file << " V" << e->chan; + // write the note or update data + if (e->is_note()) { + Alg_note_ptr n = (Alg_note_ptr) e; + double dur = n->dur; + file << " K" << n->get_identifier() << + " P" << GFMT << n->pitch; + if (in_secs) { + file << " U" << TIMFMT << dur; + } else { + file << " Q" << TIMFMT << dur; + } + file << " L" << GFMT << n->loud; + Alg_parameters_ptr p = n->parameters; + while (p) { + parameter_print(file, &(p->parm)); + p = p->next; + } + } else { // an update + assert(e->is_update()); + Alg_update_ptr u = (Alg_update_ptr) e; + if (u->get_identifier() != -1) { + file << " K" << u->get_identifier(); + } + parameter_print(file, &(u->parameter)); + } + file << "\n"; + } + } +} + +bool Alg_seq::write(const char *filename) +{ + ofstream file(filename); + if (file.fail()) return false; + write(file, units_are_seconds); + file.close(); + return true; +} diff --git a/plugins/midi_import/portsmf/license.txt b/plugins/midi_import/portsmf/license.txt new file mode 100644 index 000000000..6f86ce0d4 --- /dev/null +++ b/plugins/midi_import/portsmf/license.txt @@ -0,0 +1,40 @@ +/* + * Portsmf: Portable Standard MIDI File Library + * + * license.txt -- a copy of the Portsmf copyright notice and license information + * + * Latest version available at: http://sourceforge.net/projects/portmedia + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * Copyright (c) 2001-2006 Roger B. Dannenberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire Portsmf license; however, + * the PortMusic community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ diff --git a/plugins/midi_import/portsmf/mfmidi.cpp b/plugins/midi_import/portsmf/mfmidi.cpp new file mode 100644 index 000000000..9849321f9 --- /dev/null +++ b/plugins/midi_import/portsmf/mfmidi.cpp @@ -0,0 +1,485 @@ +/* + * Read a Standard MIDI File. Externally-assigned function pointers are + * called upon recognizing things in the file. See midifile(3). + */ + +/***************************************************************************** +* Change Log +* Date | who : Change +*-----------+----------------------------------------------------------------- +* 2-Mar-92 | GWL : created changelog; MIDIFILE_ERROR to satisfy compiler +*****************************************************************************/ + +#include "stdio.h" +#include "mfmidi.h" +#include "string.h" + +#define MIDIFILE_ERROR -1 + +/* public stuff */ +extern int abort_flag; + + +void Midifile_reader::midifile() +{ + int ntrks; + midifile_error = 0; + + ntrks = readheader(); + if (midifile_error) return; + if (ntrks <= 0) { + mferror("No tracks!"); + /* no need to return since midifile_error is set */ + } + while (ntrks-- > 0 && !midifile_error) readtrack(); +} + +int Midifile_reader::readmt(char *s, int skip) + /* read through the "MThd" or "MTrk" header string */ + /* if skip == 1, we attempt to skip initial garbage. */ +{ + int nread = 0; + char b[4]; + char buff[32]; + int c; + char *errmsg = "expecting "; + + retry: + while ( nread<4 ) { + c = Mf_getc(); + if ( c == EOF ) { + errmsg = "EOF while expecting "; + goto err; + } + b[nread++] = c; + } + /* See if we found the 4 characters we're looking for */ + if ( s[0]==b[0] && s[1]==b[1] && s[2]==b[2] && s[3]==b[3] ) + return(0); + if ( skip ) { + /* If we are supposed to skip initial garbage, */ + /* try again with the next character. */ + b[0]=b[1]; + b[1]=b[2]; + b[2]=b[3]; + nread = 3; + goto retry; + } + err: + (void) strcpy(buff,errmsg); + (void) strcat(buff,s); + mferror(buff); + return(0); +} + +int Midifile_reader::egetc() + /* read a single character and abort on EOF */ +{ + int c = Mf_getc(); + + if ( c == EOF ) { + mferror("premature EOF"); + return EOF; + } + Mf_toberead--; + return(c); +} + +int Midifile_reader::readheader() + /* read a header chunk */ +{ + int format, ntrks, division; + + if ( readmt("MThd",Mf_skipinit) == EOF ) + return(0); + + Mf_toberead = read32bit(); + if (midifile_error) return MIDIFILE_ERROR; + format = read16bit(); + if (midifile_error) return MIDIFILE_ERROR; + ntrks = read16bit(); + if (midifile_error) return MIDIFILE_ERROR; + division = read16bit(); + if (midifile_error) return MIDIFILE_ERROR; + + Mf_header(format,ntrks,division); + + /* flush any extra stuff, in case the length of header is not 6 */ + while ( Mf_toberead > 0 && !midifile_error) + (void) egetc(); + return(ntrks); +} + +void Midifile_reader::readtrack() + /* read a track chunk */ +{ + /* This array is indexed by the high half of a status byte. It's */ + /* value is either the number of bytes needed (1 or 2) for a channel */ + /* message, or 0 (meaning it's not a channel message). */ + static int chantype[] = { + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 through 0x70 */ + 2, 2, 2, 2, 1, 1, 2, 0 /* 0x80 through 0xf0 */ + }; + long lookfor, lng; + int c, c1, type; + int sysexcontinue = 0; /* 1 if last message was an unfinished sysex */ + int running = 0; /* 1 when running status used */ + int status = 0; /* (possibly running) status byte */ + int needed; + + if ( readmt("MTrk",0) == EOF ) + return; + + Mf_toberead = read32bit(); + + if (midifile_error) return; + + Mf_currtime = 0L; + + Mf_starttrack(); + + while ( Mf_toberead > 0 ) { + + Mf_currtime += readvarinum(); /* delta time */ + if (midifile_error) return; + + c = egetc(); + if (midifile_error) return; + + if ( sysexcontinue && c != 0xf7 ) { + mferror("didn't find expected continuation of a sysex"); + return; + } + if ( (c & 0x80) == 0 ) { /* running status? */ + if ( status == 0 ) { + mferror("unexpected running status"); + return; + } + running = 1; + } else { + status = c; + running = 0; + } + + needed = chantype[ (status>>4) & 0xf ]; + + if ( needed ) { /* ie. is it a channel message? */ + + if ( running ) + c1 = c; + else { + c1 = egetc(); + if (midifile_error) return; + } + chanmessage( status, c1, (needed>1) ? egetc() : 0 ); + if (midifile_error) return; + continue;; + } + + switch ( c ) { + + case 0xff: /* meta event */ + + type = egetc(); + if (midifile_error) return; + /* watch out - Don't combine the next 2 statements */ + lng = readvarinum(); + if (midifile_error) return; + lookfor = Mf_toberead - lng; + msginit(); + + while ( Mf_toberead > lookfor ) { + char c = egetc(); + if (midifile_error) return; + msgadd(c); + } + metaevent(type); + break; + + case 0xf0: /* start of system exclusive */ + + /* watch out - Don't combine the next 2 statements */ + lng = readvarinum(); + if (midifile_error) return; + lookfor = Mf_toberead - lng; + msginit(); + msgadd(0xf0); + + while ( Mf_toberead > lookfor ) { + c = egetc(); + if (midifile_error) return; + msgadd(c); + } + if ( c==0xf7 || Mf_nomerge==0 ) + sysex(); + else + sysexcontinue = 1; /* merge into next msg */ + break; + + case 0xf7: /* sysex continuation or arbitrary stuff */ + + /* watch out - Don't combine the next 2 statements */ + lng = readvarinum(); + if (midifile_error) return; + lookfor = Mf_toberead - lng; + + if ( ! sysexcontinue ) + msginit(); + + while ( Mf_toberead > lookfor ) { + c = egetc(); + if (midifile_error) return; + msgadd(c); + } + if ( ! sysexcontinue ) { + Mf_arbitrary(msgleng(), msg()); + } + else if ( c == 0xf7 ) { + sysex(); + sysexcontinue = 0; + } + break; + default: + + badbyte(c); + + break; + } + } + Mf_endtrack(); + return; +} + +void Midifile_reader::badbyte(int c) +{ + char buff[32]; + + (void) sprintf(buff,"unexpected byte: 0x%02x",c); + mferror(buff); +} + +void Midifile_reader::metaevent(int type) +{ + int leng = msgleng(); + char *m = msg(); + + switch ( type ) { + case 0x00: + Mf_seqnum(to16bit(m[0],m[1])); + break; + case 0x01: /* Text event */ + case 0x02: /* Copyright notice */ + case 0x03: /* Sequence/Track name */ + case 0x04: /* Instrument name */ + case 0x05: /* Lyric */ + case 0x06: /* Marker */ + case 0x07: /* Cue point */ + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: + /* These are all text events */ + Mf_text(type,leng,m); + break; + case 0x20: + Mf_chanprefix(m[0]); + break; + case 0x21: + Mf_portprefix(m[0]); + break; + case 0x2f: /* End of Track */ + Mf_eot(); + break; + case 0x51: /* Set tempo */ + Mf_tempo(to32bit(0,m[0],m[1],m[2])); + break; + case 0x54: + Mf_smpte(m[0],m[1],m[2],m[3],m[4]); + break; + case 0x58: + Mf_timesig(m[0],m[1],m[2],m[3]); + break; + case 0x59: + Mf_keysig(m[0],m[1]); + break; + case 0x7f: + Mf_sqspecific(leng,m); + break; + default: + Mf_metamisc(type,leng,m); + } +} + + +void Midifile_reader::sysex() +{ + Mf_sysex(msgleng(), msg()); +} + + +void Midifile_reader::chanmessage(int status, int c1, int c2) +{ + int chan = status & 0xf; + + switch ( status & 0xf0 ) { + case NOTEOFF: + Mf_off(chan,c1,c2); + break; + case NOTEON: + Mf_on(chan,c1,c2); + break; + case PRESSURE: + Mf_pressure(chan,c1,c2); + break; + case CONTROLLER: + Mf_controller(chan,c1,c2); + break; + case PITCHBEND: + Mf_pitchbend(chan,c1,c2); + break; + case PROGRAM: + Mf_program(chan,c1); + break; + case CHANPRESSURE: + Mf_chanpressure(chan,c1); + break; + } +} + +/* readvarinum - read a varying-length number, and return the */ +/* number of characters it took. */ + +long Midifile_reader::readvarinum() +{ + long value; + int c; + + c = egetc(); + if (midifile_error) return 0; + + value = (long) c; + if ( c & 0x80 ) { + value &= 0x7f; + do { + c = egetc(); + if (midifile_error) return 0; + value = (value << 7) + (c & 0x7f); + } while (c & 0x80); + } + return (value); +} + +long Midifile_reader::to32bit(int c1, int c2, int c3, int c4) +{ + long value = 0L; + + value = (c1 & 0xff); + value = (value<<8) + (c2 & 0xff); + value = (value<<8) + (c3 & 0xff); + value = (value<<8) + (c4 & 0xff); + return (value); +} + +int Midifile_reader::to16bit(int c1, int c2) +{ + return ((c1 & 0xff ) << 8) + (c2 & 0xff); +} + +long Midifile_reader::read32bit() +{ + int c1, c2, c3, c4; + + c1 = egetc(); if (midifile_error) return 0; + c2 = egetc(); if (midifile_error) return 0; + c3 = egetc(); if (midifile_error) return 0; + c4 = egetc(); if (midifile_error) return 0; + return to32bit(c1,c2,c3,c4); +} + +int Midifile_reader::read16bit() +{ + int c1, c2; + c1 = egetc(); if (midifile_error) return 0; + c2 = egetc(); if (midifile_error) return 0; + return to16bit(c1,c2); +} + +void Midifile_reader::mferror(char *s) +{ + Mf_error(s); + midifile_error = 1; +} + +/* The code below allows collection of a system exclusive message of */ +/* arbitrary length. The Msgbuff is expanded as necessary. The only */ +/* visible data/routines are msginit(), msgadd(), msg(), msgleng(). */ + +#define MSGINCREMENT 128 + +Midifile_reader::Midifile_reader() +{ + Mf_nomerge = 0; + Mf_currtime = 0L; + Mf_skipinit = 0; + Mf_toberead = 0; + + Msgbuff = 0; /* message buffer */ + Msgsize = 0; /* Size of currently allocated Msg */ + Msgindex = 0; /* index of next available location in Msg */ +} + +void Midifile_reader::finalize() +{ + if (Msgbuff) Mf_free(Msgbuff, Msgsize); + Msgbuff = NULL; +} + + +void Midifile_reader::msginit() +{ + Msgindex = 0; +} + +char *Midifile_reader::msg() +{ + return(Msgbuff); +} + +int Midifile_reader::msgleng() +{ + return(Msgindex); +} + +void Midifile_reader::msgadd(int c) +{ + /* If necessary, allocate larger message buffer. */ + if ( Msgindex >= Msgsize ) + msgenlarge(); + Msgbuff[Msgindex++] = c; +} + +void Midifile_reader::msgenlarge() +{ + char *newmess; + char *oldmess = Msgbuff; + int oldleng = Msgsize; + + Msgsize += MSGINCREMENT; + newmess = (char *) Mf_malloc((sizeof(char) * Msgsize) ); + + /* copy old message into larger new one */ + if ( oldmess != 0 ) { + register char *p = newmess; + register char *q = oldmess; + register char *endq = &oldmess[oldleng]; + + for ( ; q!=endq ; p++,q++ ) + *p = *q; + Mf_free(oldmess, oldleng); + } + Msgbuff = newmess; +} diff --git a/plugins/midi_import/portsmf/mfmidi.h b/plugins/midi_import/portsmf/mfmidi.h new file mode 100644 index 000000000..805237565 --- /dev/null +++ b/plugins/midi_import/portsmf/mfmidi.h @@ -0,0 +1,98 @@ +#define NOTEOFF 0x80 +#define NOTEON 0x90 +#define PRESSURE 0xa0 +#define CONTROLLER 0xb0 +#define PITCHBEND 0xe0 +#define PROGRAM 0xc0 +#define CHANPRESSURE 0xd0 + +/* These are the strings used in keynote to identify Standard MIDI File */ +/* meta text messages. */ + +#define METATEXT "Text Event" +#define METACOPYRIGHT "Copyright Notice" +#define METASEQUENCE "Sequence/Track Name" +#define METAINSTRUMENT "Instrument Name" +#define METALYRIC "Lyric" +#define METAMARKER "Marker" +#define METACUE "Cue Point" +#define METAUNRECOGNIZED "Unrecognized" + + +class Midifile_reader { +public: + void midifile(); + int Mf_nomerge; /* 1 => continue'ed system exclusives are */ + /* not collapsed. */ + long Mf_currtime; /* current time in delta-time units */ + int Mf_skipinit; /* 1 if initial garbage should be skipped */ + Midifile_reader(); + // call finalize() when done or you may leak memory. + void finalize(); /* clean up before deletion */ + // Note: rather than finalize, we should have ~Midifile_reader(), + // but at least VC++ complains that there is no Mf_free(), even + // though Mf_free is declared as virtual and this is an abstract + // class. I don't understand this, so finalize() is a workaround. -RBD + +protected: + int midifile_error; + + virtual void *Mf_malloc(size_t size) = 0; /* malloc() */ + virtual void Mf_free(void *obj, size_t size) = 0; /* free() */ + /* Methods to be called while processing the MIDI file. */ + virtual void Mf_starttrack() = 0; + virtual void Mf_endtrack() = 0; + virtual int Mf_getc() = 0; + virtual void Mf_chanprefix(int) = 0; + virtual void Mf_portprefix(int) = 0; + virtual void Mf_eot() = 0; + virtual void Mf_error(char *) = 0; + virtual void Mf_header(int,int,int) = 0; + virtual void Mf_on(int,int,int) = 0; + virtual void Mf_off(int,int,int) = 0; + virtual void Mf_pressure(int,int,int) = 0; + virtual void Mf_controller(int,int,int) = 0; + virtual void Mf_pitchbend(int,int,int) = 0; + virtual void Mf_program(int,int) = 0; + virtual void Mf_chanpressure(int,int) = 0; + virtual void Mf_sysex(int,char*) = 0; + virtual void Mf_arbitrary(int,char*) = 0; + virtual void Mf_metamisc(int,int,char*) = 0; + virtual void Mf_seqnum(int) = 0; + virtual void Mf_smpte(int,int,int,int,int) = 0; + virtual void Mf_timesig(int,int,int,int) = 0; + virtual void Mf_tempo(int) = 0; + virtual void Mf_keysig(int,int) = 0; + virtual void Mf_sqspecific(int,char*) = 0; + virtual void Mf_text(int,int,char*) = 0; + +private: + long Mf_toberead; + + long readvarinum(); + long read32bit(); + int read16bit(); + void msgenlarge(); + char *msg(); + int readheader(); + void readtrack(); + void sysex(); + void msginit(); + int egetc(); + int msgleng(); + + int readmt(char*,int); + long to32bit(int,int,int,int); + int to16bit(int,int); + void mferror(char *); + void badbyte(int); + void metaevent(int); + void msgadd(int); + void chanmessage(int,int,int); + + char *Msgbuff; + long Msgsize; + long Msgindex; +}; + + diff --git a/plugins/midi_import/portsmf/strparse.cpp b/plugins/midi_import/portsmf/strparse.cpp new file mode 100644 index 000000000..4c9a3529e --- /dev/null +++ b/plugins/midi_import/portsmf/strparse.cpp @@ -0,0 +1,86 @@ +#include +// #include -- for debugging (cout) +#include "ctype.h" +using namespace std; +#include "strparse.h" + +void String_parse::skip_space() +{ + while ((*str)[pos] && isspace((*str)[pos])) { + pos = pos + 1; + } +} + + +char String_parse::peek() +{ + return (*str)[pos]; +} + + +void String_parse::get_nonspace_quoted(string &field) +{ + field.clear(); + skip_space(); + bool quoted = false; + if ((*str)[pos] == '"') { + quoted = true; + field.append(1, '"'); + pos = pos + 1; + } + while ((*str)[pos] && (quoted || !isspace((*str)[pos]))) { + if ((*str)[pos] == '"') { + if (quoted) { + field.append(1, '"'); + pos = pos + 1; + } + return; + } + if ((*str)[pos] == '\\') { + pos = pos + 1; + } + if ((*str)[pos]) { + field.append(1, (*str)[pos]); + pos = pos + 1; + } + } +} + + +char *escape_chars[] = {"\\n", "\\t", "\\\\", "\\r", "\\\""}; + + +void string_escape(string &result, char *str, char *quote) +{ + int length = (int) strlen(str); + if (quote[0]) { + result.append(1, quote[0]); + } + for (int i = 0; i < length; i++) { + if (!isalnum((unsigned char) str[i])) { + char *chars = "\n\t\\\r\""; + char *special = strchr(chars, str[i]); + if (special) { + result.append(escape_chars[special - chars]); + } else { + result.append(1, str[i]); + } + } else { + result.append(1, str[i]); + } + } + result.append(1, quote[0]); +} + +void String_parse::get_remainder(std::string &field) +{ + field.clear(); + skip_space(); + int len = str->length() - pos; + if ((*str)[len - 1] == '\n') { // if str ends in newline, + len--; // reduce length to ignore newline + } + field.insert(0, *str, pos, len); +} + + diff --git a/plugins/midi_import/portsmf/strparse.h b/plugins/midi_import/portsmf/strparse.h new file mode 100644 index 000000000..7b44eb5ff --- /dev/null +++ b/plugins/midi_import/portsmf/strparse.h @@ -0,0 +1,18 @@ +// strparse.h -- header for String_parse class + +class String_parse { +public: + int pos; + std::string *str; + void init(std::string *s) { + str = s; + pos = 0; + } + void skip_space(); + char peek(); + void get_nonspace_quoted(std::string &field); + // get the remaining characters, skipping initial spaces and final return + void get_remainder(std::string &field); +}; + +void string_escape(std::string &result, char *s, char *quote); diff --git a/plugins/midi_import/portsmf/trace.cpp b/plugins/midi_import/portsmf/trace.cpp new file mode 100644 index 000000000..7c1999db5 --- /dev/null +++ b/plugins/midi_import/portsmf/trace.cpp @@ -0,0 +1,25 @@ +// trace.cpp -- debugging print function +// +// (I think this was created to provide a generic print function +// for use in non-command-line Windows applications where printf +// does not work. Currently, it is not used, but kept around for +// possible debugging needs. -RBD) + +#include "stdarg.h" +#include "stdio.h" +#include "crtdbg.h" + + +void trace(char *format, ...) +{ + char msg[256]; + va_list args; + va_start(args, format); + _vsnprintf(msg, 256, format, args); + va_end(args); +#ifdef _DEBUG + _CrtDbgReport(_CRT_WARN, NULL, NULL, NULL, msg); +#else + printf(msg); +#endif +} diff --git a/plugins/midi_import/portsmf/trace.h b/plugins/midi_import/portsmf/trace.h new file mode 100644 index 000000000..5726f0c5d --- /dev/null +++ b/plugins/midi_import/portsmf/trace.h @@ -0,0 +1,2 @@ +void trace(char *format, ...); + diff --git a/plugins/sf2_player/sf2_player.cpp b/plugins/sf2_player/sf2_player.cpp index b4ccebad4..f0143091b 100644 --- a/plugins/sf2_player/sf2_player.cpp +++ b/plugins/sf2_player/sf2_player.cpp @@ -67,6 +67,7 @@ plugin::descriptor sf2player_plugin_descriptor = // Static map of current sfonts QMap sf2Instrument::s_fonts; +QMutex sf2Instrument::s_fontsMutex; @@ -116,6 +117,8 @@ sf2Instrument::sf2Instrument( instrumentTrack * _instrument_track ) : instrumentPlayHandle * iph = new instrumentPlayHandle( this ); engine::getMixer()->addPlayHandle( iph ); + //loadFile( configManager::inst()->defaultSoundfont() ); + updateSampleRate(); updateReverbOn(); updateReverb(); @@ -240,17 +243,34 @@ void sf2Instrument::loadSettings( const QDomElement & _this ) void sf2Instrument::loadFile( const QString & _file ) { - openFile( _file ); - updatePatch(); + if( !_file.isEmpty() ) + { + openFile( _file ); + updatePatch(); - // for some reason we've to call that, otherwise preview of a - // soundfont for the first time fails - updateSampleRate(); + // for some reason we've to call that, otherwise preview of a + // soundfont for the first time fails + updateSampleRate(); + } } +automatableModel * sf2Instrument::getChildModel( const QString & _modelName ) +{ + if( _modelName == "bank" ) + { + return &m_bankNum; + } + else if( _modelName == "patch" ) + { + return &m_patchNum; + } +} + + + QString sf2Instrument::nodeName( void ) const { return( sf2player_plugin_descriptor.name ); @@ -267,6 +287,7 @@ void sf2Instrument::freeFont( void ) if ( m_font != NULL ) { + s_fontsMutex.lock(); --(m_font->refCount); // No more references @@ -286,6 +307,7 @@ void sf2Instrument::freeFont( void ) fluid_synth_remove_sfont( m_synth, m_font->fluidFont ); } + s_fontsMutex.unlock(); m_font = NULL; } @@ -298,12 +320,15 @@ void sf2Instrument::openFile( const QString & _sf2File ) { emit fileLoading(); - char * sf2Ascii = qstrdup( qPrintable( _sf2File ) ); + // Used for loading file + char * sf2Ascii = qstrdup( qPrintable( + sampleBuffer::tryToMakeAbsolute( _sf2File ) ) ); // free reference to soundfont if one is selected freeFont(); m_synthMutex.lock(); + s_fontsMutex.lock(); // Increment Reference if( s_fonts.contains( _sf2File ) ) @@ -335,13 +360,16 @@ void sf2Instrument::openFile( const QString & _sf2File ) } } + s_fontsMutex.unlock(); m_synthMutex.unlock(); if( m_fontId >= 0 ) { - m_patchNum.setValue( 0 ); - m_bankNum.setValue( 0 ); - m_filename = _sf2File; + // Don't reset patch/bank, so that it isn't cleared when + // someone resolves a missing file + //m_patchNum.setValue( 0 ); + //m_bankNum.setValue( 0 ); + m_filename = sampleBuffer::tryToMakeRelative( _sf2File ); emit fileChanged(); } @@ -922,7 +950,7 @@ void sf2InstrumentView::showFileDialog( void ) ofd.setFileMode( QFileDialog::ExistingFiles ); QStringList types; - types << tr( "SoundFont2-Files (*.sf2)" ); + types << tr( "SoundFont2 Files (*.sf2)" ); ofd.setFilters( types ); QString dir; diff --git a/plugins/sf2_player/sf2_player.h b/plugins/sf2_player/sf2_player.h index 75d9d0e53..518b492c5 100644 --- a/plugins/sf2_player/sf2_player.h +++ b/plugins/sf2_player/sf2_player.h @@ -47,6 +47,9 @@ class QLabel; class sf2Instrument : public instrument { Q_OBJECT + mapPropertyFromModel(int,getBank,setBank,m_bankNum); + mapPropertyFromModel(int,getPatch,setPatch,m_patchNum); + public: sf2Instrument( instrumentTrack * _instrument_track ); virtual ~sf2Instrument(); @@ -64,6 +67,8 @@ public: virtual void loadFile( const QString & _file ); + virtual automatableModel * getChildModel( const QString & _modelName ); + virtual QString nodeName( void ) const; virtual f_cnt_t desiredReleaseFrames( void ) const @@ -81,6 +86,8 @@ public: QString getCurrentPatchName(); + void setParameter( const QString & _param, const QString & _value ); + public slots: void openFile( const QString & _sf2File ); void updatePatch( void ); @@ -96,6 +103,7 @@ public slots: private: + static QMutex s_fontsMutex; static QMap s_fonts; static int (* s_origFree)( fluid_sfont_t * ); diff --git a/src/core/config_mgr.cpp b/src/core/config_mgr.cpp index 5dbf1fb6e..76a0d1f1e 100644 --- a/src/core/config_mgr.cpp +++ b/src/core/config_mgr.cpp @@ -134,6 +134,16 @@ void configManager::setSTKDir( const QString & _fd ) +void configManager::setDefaultSoundfont( const QString & _sf ) +{ +#ifdef LMMS_HAVE_FLUIDSYNTH + m_defaultSoundfont = _sf; +#endif +} + + + + void configManager::addRecentlyOpenedProject( const QString & _file ) { m_recentlyOpenedProjects.removeAll( _file ); @@ -270,6 +280,9 @@ bool configManager::loadConfigFile( void ) #ifdef LMMS_HAVE_STK setSTKDir( value( "paths", "stkdir" ) ); #endif +#ifdef LMMS_HAVE_FLUIDSYNTH + setDefaultSoundfont( value( "paths", "defaultsf2" ) ); +#endif if( m_vstDir == "" ) { @@ -297,6 +310,7 @@ bool configManager::loadConfigFile( void ) } #endif + QDir::setSearchPaths( "resources", QStringList() << artworkDir() << defaultArtworkDir() ); @@ -335,6 +349,9 @@ void configManager::saveConfigFile( void ) #ifdef LMMS_HAVE_STK setValue( "paths", "stkdir", m_stkDir ); #endif +#ifdef LMMS_HAVE_FLUIDSYNTH + setValue( "paths", "defaultsf2", m_defaultSoundfont ); +#endif QDomDocument doc( "lmms-config-file" ); diff --git a/src/core/plugin.cpp b/src/core/plugin.cpp index 5c51d1104..33a77ff6a 100644 --- a/src/core/plugin.cpp +++ b/src/core/plugin.cpp @@ -83,9 +83,9 @@ void plugin::loadFile( const QString & ) -QString plugin::getParameter( const QString & ) +automatableModel * plugin::getChildModel( const QString & ) { - return( "" ); + return NULL; } diff --git a/src/gui/setup_dialog.cpp b/src/gui/setup_dialog.cpp index 669522f23..c9ec0f8ff 100644 --- a/src/gui/setup_dialog.cpp +++ b/src/gui/setup_dialog.cpp @@ -103,6 +103,9 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : m_artworkDir( configManager::inst()->artworkDir() ), m_flDir( configManager::inst()->flDir() ), m_ladDir( configManager::inst()->ladspaDir() ), +#ifdef LMMS_HAVE_FLUIDSYNTH + m_defaultSoundfont( configManager::inst()->defaultSoundfont() ), +#endif #ifdef LMMS_HAVE_STK m_stkDir( configManager::inst()->stkDir() ), #endif @@ -130,11 +133,14 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : m_tabBar->setFixedWidth( 72 ); QWidget * ws = new QWidget( settings ); + int wsHeight = 336; #ifdef LMMS_HAVE_STK - ws->setFixedSize( 360, 422 ); -#else - ws->setFixedSize( 360, 366 ); + wsHeight += 50; #endif +#ifdef LMMS_HAVE_FLUIDSYNTH + wsHeight += 50; +#endif + ws->setFixedSize( 360, wsHeight ); QWidget * general = new QWidget( ws ); general->setFixedSize( 360, 240 ); QVBoxLayout * gen_layout = new QVBoxLayout( general ); @@ -228,22 +234,25 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : - QWidget * directories = new QWidget( ws ); + QWidget * paths = new QWidget( ws ); + int pathsHeight = 296; #ifdef LMMS_HAVE_STK - directories->setFixedSize( 360, 372 ); -#else - directories->setFixedSize( 360, 326 ); + pathsHeight += 50; #endif - QVBoxLayout * dir_layout = new QVBoxLayout( directories ); +#ifdef LMMS_HAVE_FLUIDSYNTH + pathsHeight += 50; +#endif + paths->setFixedSize( 360, pathsHeight ); + QVBoxLayout * dir_layout = new QVBoxLayout( paths ); dir_layout->setSpacing( 0 ); dir_layout->setMargin( 0 ); - labelWidget( directories, tr( "Directories" ) ); + labelWidget( paths, tr( "Paths" ) ); // working-dir tabWidget * lmms_wd_tw = new tabWidget( tr( "LMMS working directory" ).toUpper(), - directories ); - lmms_wd_tw->setFixedHeight( 56 ); + paths ); + lmms_wd_tw->setFixedHeight( 48 ); m_wdLineEdit = new QLineEdit( m_workingDir, lmms_wd_tw ); m_wdLineEdit->setGeometry( 10, 20, 300, 16 ); @@ -254,15 +263,15 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : embed::getIconPixmap( "project_open", 16, 16 ), "", lmms_wd_tw ); workingdir_select_btn->setFixedSize( 24, 24 ); - workingdir_select_btn->move( 320, 20 ); + workingdir_select_btn->move( 320, 16 ); connect( workingdir_select_btn, SIGNAL( clicked() ), this, SLOT( openWorkingDir() ) ); // vst-dir tabWidget * vst_tw = new tabWidget( tr( "VST-plugin directory" ).toUpper(), - directories ); - vst_tw->setFixedHeight( 56 ); + paths ); + vst_tw->setFixedHeight( 48 ); m_vdLineEdit = new QLineEdit( m_vstDir, vst_tw ); m_vdLineEdit->setGeometry( 10, 20, 300, 16 ); @@ -273,15 +282,15 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : embed::getIconPixmap( "project_open", 16, 16 ), "", vst_tw ); vstdir_select_btn->setFixedSize( 24, 24 ); - vstdir_select_btn->move( 320, 20 ); + vstdir_select_btn->move( 320, 16 ); connect( vstdir_select_btn, SIGNAL( clicked() ), this, SLOT( openVSTDir() ) ); // artwork-dir tabWidget * artwork_tw = new tabWidget( tr( "Artwork directory" ).toUpper(), - directories ); - artwork_tw->setFixedHeight( 56 ); + paths ); + artwork_tw->setFixedHeight( 48 ); m_adLineEdit = new QLineEdit( m_artworkDir, artwork_tw ); m_adLineEdit->setGeometry( 10, 20, 300, 16 ); @@ -292,15 +301,18 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : embed::getIconPixmap( "project_open", 16, 16 ), "", artwork_tw ); artworkdir_select_btn->setFixedSize( 24, 24 ); - artworkdir_select_btn->move( 320, 20 ); + artworkdir_select_btn->move( 320, 16 ); connect( artworkdir_select_btn, SIGNAL( clicked() ), this, SLOT( openArtworkDir() ) ); + + + // FL Studio-dir tabWidget * fl_tw = new tabWidget( tr( "FL Studio installation directory" ).toUpper(), - directories ); - fl_tw->setFixedHeight( 56 ); + paths ); + fl_tw->setFixedHeight( 48 ); m_fdLineEdit = new QLineEdit( m_flDir, fl_tw ); m_fdLineEdit->setGeometry( 10, 20, 300, 16 ); @@ -311,14 +323,14 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : embed::getIconPixmap( "project_open", 16, 16 ), "", fl_tw ); fldir_select_btn->setFixedSize( 24, 24 ); - fldir_select_btn->move( 320, 20 ); + fldir_select_btn->move( 320, 16 ); connect( fldir_select_btn, SIGNAL( clicked() ), this, SLOT( openFLDir() ) ); // LADSPA-dir tabWidget * lad_tw = new tabWidget( tr( - "LADSPA plugin directories" ).toUpper(), - directories ); - lad_tw->setFixedHeight( 56 ); + "LADSPA plugin paths" ).toUpper(), + paths ); + lad_tw->setFixedHeight( 48 ); m_ladLineEdit = new QLineEdit( m_ladDir, lad_tw ); m_ladLineEdit->setGeometry( 10, 20, 300, 16 ); @@ -329,7 +341,7 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : embed::getIconPixmap( "project_open", 16, 16 ), "", lad_tw ); laddir_select_btn->setFixedSize( 24, 24 ); - laddir_select_btn->move( 320, 20 ); + laddir_select_btn->move( 320, 16 ); connect( laddir_select_btn, SIGNAL( clicked() ), this, SLOT( openLADSPADir() ) ); @@ -337,8 +349,8 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : // STK-dir tabWidget * stk_tw = new tabWidget( tr( "STK rawwave directory" ).toUpper(), - directories ); - stk_tw->setFixedHeight( 56 ); + paths ); + stk_tw->setFixedHeight( 48 ); m_stkLineEdit = new QLineEdit( m_stkDir, stk_tw ); m_stkLineEdit->setGeometry( 10, 20, 300, 16 ); @@ -349,11 +361,32 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : embed::getIconPixmap( "project_open", 16, 16 ), "", stk_tw ); stkdir_select_btn->setFixedSize( 24, 24 ); - stkdir_select_btn->move( 320, 20 ); + stkdir_select_btn->move( 320, 16 ); connect( stkdir_select_btn, SIGNAL( clicked() ), this, SLOT( openSTKDir() ) ); #endif +#ifdef LMMS_HAVE_FLUIDSYNTH + // Soundfont + tabWidget * sf_tw = new tabWidget( tr( + "Default Soundfont File" ).toUpper(), paths ); + sf_tw->setFixedHeight( 48 ); + + m_sfLineEdit = new QLineEdit( m_defaultSoundfont, sf_tw ); + m_sfLineEdit->setGeometry( 10, 20, 300, 16 ); + connect( m_sfLineEdit, SIGNAL( textChanged( const QString & ) ), this, + SLOT( setDefaultSoundfont( const QString & ) ) ); + + QPushButton * sf_select_btn = new QPushButton( + embed::getIconPixmap( "project_open", 16, 16 ), + "", sf_tw ); + sf_select_btn->setFixedSize( 24, 24 ); + sf_select_btn->move( 320, 16 ); + connect( sf_select_btn, SIGNAL( clicked() ), this, + SLOT( openDefaultSoundfont() ) ); +#endif + + dir_layout->addWidget( lmms_wd_tw ); dir_layout->addSpacing( 10 ); dir_layout->addWidget( vst_tw ); @@ -366,6 +399,10 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : #ifdef LMMS_HAVE_STK dir_layout->addSpacing( 10 ); dir_layout->addWidget( stk_tw ); +#endif +#ifdef LMMS_HAVE_FLUIDSYNTH + dir_layout->addSpacing( 10 ); + dir_layout->addWidget( sf_tw ); #endif dir_layout->addStretch(); @@ -373,7 +410,6 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : - QWidget * performance = new QWidget( ws ); performance->setFixedSize( 360, 240 ); QVBoxLayout * perf_layout = new QVBoxLayout( performance ); @@ -584,7 +620,7 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : m_tabBar->addTab( general, tr( "General settings" ), 0, FALSE, TRUE )->setIcon( embed::getIconPixmap( "setup_general" ) ); - m_tabBar->addTab( directories, tr( "Directories" ), 1, FALSE, TRUE + m_tabBar->addTab( paths, tr( "Paths" ), 1, FALSE, TRUE )->setIcon( embed::getIconPixmap( "setup_directories" ) ); m_tabBar->addTab( performance, tr( "Performance settings" ), 2, FALSE, @@ -676,6 +712,9 @@ void setupDialog::accept( void ) configManager::inst()->setArtworkDir( m_artworkDir ); configManager::inst()->setFLDir( m_flDir ); configManager::inst()->setLADSPADir( m_ladDir ); +#ifdef LMMS_HAVE_FLUIDSYNTH + configManager::inst()->setDefaultSoundfont( m_defaultSoundfont ); +#endif #ifdef LMMS_HAVE_STK configManager::inst()->setSTKDir( m_stkDir ); #endif @@ -940,6 +979,23 @@ void setupDialog::openSTKDir( void ) +void setupDialog::openDefaultSoundfont( void ) +{ +#ifdef LMMS_HAVE_FLUIDSYNTH + QString new_file = QFileDialog::getOpenFileName( this, + tr( "Choose defeault SoundFont" ), m_defaultSoundfont, + "SoundFont2 Files (*.sf2)" ); + + if( new_file != QString::null ) + { + m_sfLineEdit->setText( new_file ); + } +#endif +} + + + + void setupDialog::setFLDir( const QString & _fd ) { m_flDir = _fd; @@ -966,6 +1022,16 @@ void setupDialog::setSTKDir( const QString & _fd ) +void setupDialog::setDefaultSoundfont( const QString & _sf ) +{ +#ifdef LMMS_HAVE_FLUIDSYNTH + m_defaultSoundfont = _sf; +#endif +} + + + + void setupDialog::audioInterfaceChanged( const QString & _iface ) { for( aswMap::iterator it = m_audioIfaceSetupWidgets.begin();