/* * MidiImport.cpp - support for importing MIDI files * * Copyright (c) 2005-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include "MidiImport.h" #include "track_container.h" #include "InstrumentTrack.h" #include "AutomationTrack.h" #include "AutomationPattern.h" #include "config_mgr.h" #include "pattern.h" #include "Instrument.h" #include "MainWindow.h" #include "debug.h" #include "embed.h" #include "song.h" #include "portsmf/allegro.h" #define makeID(_c0, _c1, _c2, _c3) \ ( 0 | \ ( ( _c0 ) | ( ( _c1 ) << 8 ) | ( ( _c2 ) << 16 ) | ( ( _c3 ) << 24 ) ) ) extern "C" { Plugin::Descriptor PLUGIN_EXPORT midiimport_plugin_descriptor = { STRINGIFY( PLUGIN_NAME ), "MIDI Import", QT_TRANSLATE_NOOP( "pluginBrowser", "Filter for importing MIDI-files into LMMS" ), "Tobias Doerffel ", 0x0100, Plugin::ImportFilter, NULL, NULL, NULL } ; } MidiImport::MidiImport( const QString & _file ) : ImportFilter( _file, &midiimport_plugin_descriptor ), m_events(), m_timingDivision( 0 ) { } MidiImport::~MidiImport() { } bool MidiImport::tryImport( trackContainer * _tc ) { if( openFile() == false ) { return false; } #ifdef LMMS_HAVE_FLUIDSYNTH if( engine::hasGUI() && configManager::inst()->defaultSoundfont().isEmpty() ) { QMessageBox::information( engine::mainWindow(), tr( "Setup incomplete" ), tr( "You do not have set up a default soundfont in " "the settings dialog (Edit->Settings). " "Therefore no sound will be played back after " "importing this MIDI file. You should download " "a General MIDI soundfont, specify it in " "settings dialog and try again." ) ); } #else if( engine::hasGUI() ) { QMessageBox::information( engine::mainWindow(), tr( "Setup incomplete" ), tr( "You did not compile LMMS with support for " "SoundFont2 player, which is used to add default " "sound to imported MIDI files. " "Therefore no sound will be played back after " "importing this MIDI file." ) ); } #endif switch( readID() ) { case makeID( 'M', 'T', 'h', 'd' ): printf( "MidiImport::tryImport(): found MThd\n"); return readSMF( _tc ); case makeID( 'R', 'I', 'F', 'F' ): printf( "MidiImport::tryImport(): found RIFF\n"); return readRIFF( _tc ); default: printf( "MidiImport::tryImport(): not a Standard MIDI " "file\n" ); return false; } } class smfMidiCC { public: smfMidiCC() : at( NULL ), ap( NULL ), lastPos( 0 ) { } AutomationTrack * at; AutomationPattern * ap; midiTime lastPos; smfMidiCC & create( trackContainer * _tc ) { if( !at ) { at = dynamic_cast( track::create( track::AutomationTrack, _tc ) ); } return *this; } 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->childModel( "bank" )->setValue( 0 ); it_inst->childModel( "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::mainWindow() ); 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(); if( tap ) { 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", (int) 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() - 12, 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->childModel( "bank" )->setValue( 0 ); ch->it_inst->childModel( "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->childModel( "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", (int) 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; } bool MidiImport::readRIFF( trackContainer * _tc ) { // skip file length skip( 4 ); // check file type ("RMID" = RIFF MIDI) if( readID() != makeID( 'R', 'M', 'I', 'D' ) ) { invalid_format: qWarning( "MidiImport::readRIFF(): invalid file format" ); return false; } // search for "data" chunk while( 1 ) { const int id = readID(); const int len = read32LE(); if( file().atEnd() ) { data_not_found: qWarning( "MidiImport::readRIFF(): data chunk not found" ); 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 ); } void MidiImport::error() { printf( "MidiImport::readTrack(): invalid MIDI data (offset %#x)\n", (unsigned int) file().pos() ); } extern "C" { // necessary for getting instance out of shared lib Plugin * PLUGIN_EXPORT lmms_plugin_main( Model *, void * _data ) { return new MidiImport( QString::fromUtf8( static_cast( _data ) ) ); } }