diff --git a/ChangeLog b/ChangeLog index bba925095..151fcde11 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +2007-07-23 Tobias Doerffel + + * plugins/midi_import/midi_import.h: + * plugins/midi_import/midi_import.cpp: + - when adding notes to pattern *never* let them be quantized - fixes + bug where notes of imported MIDI-files were not where they should have + been + - added support for tempo-change-events by modifying + automation-pattern of tempo-knob of track-container (if such exists) + - import track-names + - proper handling of time-base, -division etc. + + * include/midi.h: + added midiMetaEvents-enum + + * include/song_editor.h: + * include/track_container.h: + * src/core/song_editor.cpp: + added virtual tempoAutomationPattern()-method which returns + automation-pattern of tempo-object (i.e. bpmSpinBox) if such exists + for a certain track-container + + * include/automation_pattern.h: + - added non-const version of object()-method + - made lot of other methods const + 2007-07-21 Tobias Doerffel * include/mixer.h: diff --git a/configure.in b/configure.in index 3660d045f..2b8713dce 100644 --- a/configure.in +++ b/configure.in @@ -2,8 +2,8 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.50) -AC_INIT(lmms, 0.2.1-svn20070721, lmms-devel/at/lists/dot/sf/dot/net) -AM_INIT_AUTOMAKE(lmms, 0.2.1-svn20070721) +AC_INIT(lmms, 0.2.1-svn20070723, lmms-devel/at/lists/dot/sf/dot/net) +AM_INIT_AUTOMAKE(lmms, 0.2.1-svn20070723) AM_CONFIG_HEADER(config.h) diff --git a/include/automation_pattern.h b/include/automation_pattern.h index d8a866f99..f0ad02e90 100644 --- a/include/automation_pattern.h +++ b/include/automation_pattern.h @@ -95,19 +95,24 @@ public: return( classNodeName() ); } - inline const track * getTrack( void ) + inline const track * getTrack( void ) const { return( m_track ); } - inline const levelObject * object( void ) + inline const levelObject * object( void ) const + { + return( m_object ); + } + + inline levelObject * object( void ) { return( m_object ); } void processMidiTime( const midiTime & _time ); - inline bool updateFirst( void ) + inline bool updateFirst( void ) const { return( m_update_first ); } @@ -117,7 +122,7 @@ public: m_update_first = _update; } - void forgetTrack() + void forgetTrack( void ) { m_track = NULL; } diff --git a/include/midi.h b/include/midi.h index 12d50f8bd..16a76de97 100644 --- a/include/midi.h +++ b/include/midi.h @@ -59,7 +59,23 @@ enum midiEventTypes MIDI_SYSTEM_RESET = 0xFF, // meta event - for midi files only MIDI_META_EVENT = 0xFF, - MIDI_TEMPO = 0xFF51 +} ; + +enum midiMetaEvents +{ + MIDI_COPYRIGHT = 0x02, + MIDI_TRACK_NAME = 0x03, + MIDI_INST_NAME = 0x04, + MIDI_LYRIC = 0x05, + MIDI_MARKER = 0x06, + MIDI_CUE_POINT = 0x07, + MIDI_PORT_NUMBER = 0x21, + MIDI_EOT = 0x2f, + MIDI_SET_TEMPO = 0x51, + MIDI_SMPTE_OFFSET = 0x54, + MIDI_TIME_SIGNATURE = 0x58, + MIDI_KEY_SIGNATURE = 0x59, + MIDI_SEQUENCER_EVENT = 0x7f } ; diff --git a/include/song_editor.h b/include/song_editor.h index 969b72d71..54d548108 100644 --- a/include/song_editor.h +++ b/include/song_editor.h @@ -130,6 +130,7 @@ public: bpm_t getTempo( void ); + virtual automationPattern * tempoAutomationPattern( void ); // every function that replaces current file (e.g. creates new file, diff --git a/include/track_container.h b/include/track_container.h index eae126a74..e38b2ae5d 100644 --- a/include/track_container.h +++ b/include/track_container.h @@ -82,6 +82,11 @@ public: return( m_currentPosition ); } + virtual automationPattern * tempoAutomationPattern( void ) + { + return( NULL ); + } + virtual bool fixedTCOs( void ) const { return( FALSE ); diff --git a/plugins/midi_import/midi_import.cpp b/plugins/midi_import/midi_import.cpp index f323e6a1c..f393d3e1c 100644 --- a/plugins/midi_import/midi_import.cpp +++ b/plugins/midi_import/midi_import.cpp @@ -27,9 +27,12 @@ #include "track_container.h" #include "instrument_track.h" #include "pattern.h" +#include "automation_pattern.h" +#include "level_object.h" #include "debug.h" + #ifdef QT4 #include @@ -74,8 +77,7 @@ plugin::descriptor midiimport_plugin_descriptor = midiImport::midiImport( const QString & _file ) : importFilter( _file, &midiimport_plugin_descriptor ), m_events(), - m_smpteTiming( FALSE ), - m_tempo( 120 ) + m_timingDivision( 0 ) { } @@ -114,6 +116,26 @@ bool midiImport::tryImport( trackContainer * _tc ) +static inline unsigned long gcd( unsigned long a, unsigned long b ) +{ + unsigned long r; + if( a < b ) + { + r = a; + a = b; + b = r; + } + while( ( r = a % b ) != 0 ) + { + a = b; + b = r; + } + return b; +} + + + + bool FASTCALL midiImport::readSMF( trackContainer * _tc ) { // the curren position is immediately after the "MThd" id @@ -145,15 +167,14 @@ invalid_format: printf( "tracks: %d\n", num_tracks ); #endif - int time_division = readInt( 2 ); - if( time_division < 0 ) + m_timingDivision = readInt( 2 ); + if( m_timingDivision < 0 ) { goto invalid_format; } #ifdef LMMS_DEBUG printf( "time-division: %d\n", time_division ); #endif - m_smpteTiming = !!( time_division & 0x8000 ); #ifdef QT4 QProgressDialog pd( trackContainer::tr( "Importing MIDI-file..." ), @@ -166,6 +187,22 @@ invalid_format: pd.setWindowTitle( trackContainer::tr( "Please wait..." ) ); pd.show(); + // calculate some timing stuff + int crotchet_time = 16; + 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(); + if( tap != NULL ) + { + tap->object()->setLevel( 120 ); + tap->putValue( 0, 120 ); + } + // read tracks for( int i = 0; i < num_tracks; ++i ) { @@ -211,33 +248,64 @@ invalid_format: continue; } - if( !readTrack( file().pos() + len ) ) + 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 == MIDI_META_EVENT ) + { + switch( ev.m_data.m_param[0] ) + { + case MIDI_SET_TEMPO: + { + tap->putValue( midiTime( ( tick * multiplier ) / divisor ), + ev.m_data.m_param[1], FALSE ); + break; + } + default: + break; + } + } + } continue; } - // now create new channel-track for reading track - instrumentTrack * ct = dynamic_cast( + // now create new instrument-track for reading in track + instrumentTrack * it = dynamic_cast( track::create( track::INSTRUMENT_TRACK, _tc ) ); #ifdef LMMS_DEBUG - assert( ct != NULL ); + assert( it != NULL ); #endif // TODO: setup program, channel etc. - ct->loadInstrument( "tripleoscillator" ); - ct->toggledInstrumentTrackButton( FALSE ); + it->loadInstrument( "tripleoscillator" ); + it->toggledInstrumentTrackButton( 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( ct->createTCO( 0 ) ); + pattern * p = dynamic_cast( it->createTCO( 0 ) ); #ifdef LMMS_DEBUG assert( p != NULL ); #endif - ct->addTCO( p ); + it->addTCO( p ); // init keys int keys[NOTES_PER_OCTAVE * OCTAVES][2]; @@ -273,23 +341,33 @@ invalid_format: NOTES_PER_OCTAVE * OCTAVES && keys[ev.key()][0] >= 0 ) { - note n( midiTime( ( tick - keys[ev.key()][0] ) * 16 / m_tempo ), - midiTime( keys[ev.key()][0] * 16 / m_tempo ), + note n( midiTime( ( ( tick - keys[ev.key()][0] ) * multiplier ) / divisor ), + midiTime( ( keys[ev.key()][0] * multiplier ) / divisor ), (tones)( ev.key() % NOTES_PER_OCTAVE ), (octaves)( ev.key() / NOTES_PER_OCTAVE ), keys[ev.key()][1] * 100 / 128 ); - p->addNote( n ); + p->addNote( n, FALSE ); keys[ev.key()][0] = -1; } break; - case MIDI_TEMPO: - +/* case MIDI_META_EVENT: + { + switch( ev.m_data.m_param[0] ) + { + case MIDI_SET_TEMPO: + { + break; + } + default: + break; + } break; + }*/ default: -/* printf( "Unhandled event: %#x\n", - ev.m_type );*/ + /* printf( "Unhandled event: %#x\n", + (int) ev.m_type );*/ break; } } @@ -345,7 +423,7 @@ data_not_found: -bool FASTCALL midiImport::readTrack( int _track_end ) +bool FASTCALL midiImport::readTrack( int _track_end, QString & _track_name ) { int tick = 0; unsigned char last_cmd = 0; @@ -460,7 +538,17 @@ bool FASTCALL midiImport::readTrack( int _track_end ) }*/ switch( c ) { - case 0x21: // port number + case MIDI_TRACK_NAME: + if( len > 0 ) + { + char * n = new char[len+1]; + readBlock( n, len ); + n[len] = 0; + _track_name += n; + delete[] n; + } + break; + case MIDI_PORT_NUMBER: if( len < 1 ) { error(); @@ -472,49 +560,50 @@ bool FASTCALL midiImport::readTrack( int _track_end ) skip( len ); break; - case 0x2F: // end of track + case MIDI_EOT: //track->end_tick = tick; skip( _track_end - file().pos() ); return( TRUE ); - case 0x51: // tempo + case MIDI_SET_TEMPO: // tempo + { if( len < 3 ) { error(); return( FALSE ); } - if( m_smpteTiming ) - { - // SMPTE timing - // doesnt change - skip( len ); - } - else - { - int tempo = readByte() << 16; - tempo |= readByte() << 8; - tempo |= readByte(); - tempo = ( 60*1000*1000 ) / tempo; - m_events.push_back( qMakePair( tick, - midiEvent( MIDI_TEMPO, - 0, - tempo, - 0 ) ) ); -/* event = new_event(track, 0); - event->type = SND_SEQ_EVENT_TEMPO; - event->port = port; - event->tick = tick; - event->data.tempo = read_byte() << 16; - event->data.tempo |= read_byte() << 8; - event->data.tempo |= read_byte(); - skip( len -3 );*/ - skip( len-3 ); - } + int tempo = readByte() << 16; + tempo |= readByte() << 8; + tempo |= readByte(); + tempo = ( 60*1000*1000 ) / tempo; + m_events.push_back( qMakePair( tick, midiEvent( MIDI_META_EVENT, 0, MIDI_SET_TEMPO, tempo ) ) ); break; - + } + case MIDI_TIME_SIGNATURE: + { + int nominator = readByte(); + int denominator = 1 << (int) readByte(); + int clocks = readByte(); + int notes = readByte(); + if( nominator == 0 ) + { + nominator = 4; + } + if( denominator == 0 ) + { + denominator = 4; + } +#ifdef LMMS_DEBUG + printf("nom:%d denom:%d clocks:%d notes:%d\n",nominator,denominator,clocks,notes); +#endif + break; + } default:// ignore all other // meta events +#ifdef LMMS_DEBUG + printf("meta event %d\n", (int) c ); +#endif skip( len ); break; } diff --git a/plugins/midi_import/midi_import.h b/plugins/midi_import/midi_import.h index 7d3973145..1e0b9aa71 100644 --- a/plugins/midi_import/midi_import.h +++ b/plugins/midi_import/midi_import.h @@ -64,7 +64,7 @@ private: bool FASTCALL readSMF( trackContainer * _tc ); bool FASTCALL readRIFF( trackContainer * _tc ); - bool FASTCALL readTrack( int _track_end ); + bool FASTCALL readTrack( int _track_end, QString & _track_name ); void error( void ); @@ -133,8 +133,7 @@ private: typedef vvector > eventVector; eventVector m_events; - bool m_smpteTiming; - int m_tempo; + int m_timingDivision; } ; diff --git a/src/core/song_editor.cpp b/src/core/song_editor.cpp index 659162aa4..414b806c1 100644 --- a/src/core/song_editor.cpp +++ b/src/core/song_editor.cpp @@ -1355,6 +1355,14 @@ bpm_t songEditor::getTempo( void ) +automationPattern * songEditor::tempoAutomationPattern( void ) +{ + return( m_bpmSpinBox->getAutomationPattern() ); +} + + + + bool songEditor::mayChangeProject( void ) { if( m_modified == FALSE )