123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659 |
- /*
- * MidiImport.cpp - support for importing MIDI files
- *
- * Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
- *
- * This file is part of LMMS - https://lmms.io
- *
- * 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 <QDomDocument>
- #include <QDir>
- #include <QApplication>
- #include <QFile>
- #include <QMessageBox>
- #include <QProgressDialog>
- #include <sstream>
- #include <unordered_map>
- #include "MidiImport.h"
- #include "TrackContainer.h"
- #include "InstrumentTrack.h"
- #include "AutomationTrack.h"
- #include "AutomationPattern.h"
- #include "ConfigManager.h"
- #include "Pattern.h"
- #include "Instrument.h"
- #include "GuiApplication.h"
- #include "MainWindow.h"
- #include "TimePos.h"
- #include "debug.h"
- #include "Song.h"
- #include "embed.h"
- #include "plugin_export.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 <tobydox/at/users/dot/sf/dot/net>",
- 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( gui != NULL &&
- ConfigManager::inst()->sf2File().isEmpty() )
- {
- QMessageBox::information( gui->mainWindow(),
- tr( "Setup incomplete" ),
- tr( "You have not 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( gui )
- {
- QMessageBox::information( gui->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;
- TimePos lastPos;
-
- smfMidiCC & create( TrackContainer* tc, QString tn )
- {
- if( !at )
- {
- // Keep LMMS responsive, for now the import runs
- // in the main thread. This should probably be
- // removed if that ever changes.
- qApp->processEvents();
- at = dynamic_cast<AutomationTrack *>( Track::create( Track::AutomationTrack, tc ) );
- }
- if( tn != "") {
- at->setName( tn );
- }
- return *this;
- }
- void clear()
- {
- at = NULL;
- ap = NULL;
- lastPos = 0;
- }
- smfMidiCC & putValue( TimePos time, AutomatableModel * objModel, float value )
- {
- if( !ap || time > lastPos + DefaultTicksPerBar )
- {
- TimePos pPos = TimePos( time.getBar(), 0 );
- ap = dynamic_cast<AutomationPattern*>(
- at->createTCO(pPos));
- ap->addObject( objModel );
- }
- lastPos = time;
- time = time - ap->startPosition();
- ap->putValue( time, value, false );
- ap->changeLength( TimePos( time.getBar() + 1, 0 ) );
- return *this;
- }
- };
- class smfMidiChannel
- {
- public:
- smfMidiChannel() :
- it( NULL ),
- p( NULL ),
- it_inst( NULL ),
- isSF2( false ),
- hasNotes( false )
- { }
-
- InstrumentTrack * it;
- Pattern* p;
- Instrument * it_inst;
- bool isSF2;
- bool hasNotes;
- QString trackName;
-
- smfMidiChannel * create( TrackContainer* tc, QString tn )
- {
- if( !it ) {
- // Keep LMMS responsive
- qApp->processEvents();
- it = dynamic_cast<InstrumentTrack *>( Track::create( Track::InstrumentTrack, tc ) );
- #ifdef LMMS_HAVE_FLUIDSYNTH
- it_inst = it->loadInstrument( "sf2player" );
-
- if( it_inst )
- {
- isSF2 = true;
- it_inst->loadFile( ConfigManager::inst()->sf2File() );
- 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
- trackName = tn;
- if( trackName != "") {
- it->setName( tn );
- }
- // General MIDI default
- it->pitchRangeModel()->setInitValue( 2 );
- // Create a default pattern
- p = dynamic_cast<Pattern*>(it->createTCO(0));
- }
- return this;
- }
- void addNote( Note & n )
- {
- if (!p)
- {
- p = dynamic_cast<Pattern*>(it->createTCO(0));
- }
- p->addNote(n, false);
- hasNotes = true;
- }
- void splitPatterns()
- {
- Pattern * newPattern = nullptr;
- TimePos lastEnd(0);
- p->rearrangeAllNotes();
- for (auto n : p->notes())
- {
- if (!newPattern || n->pos() > lastEnd + DefaultTicksPerBar)
- {
- TimePos pPos = TimePos(n->pos().getBar(), 0);
- newPattern = dynamic_cast<Pattern*>(it->createTCO(pPos));
- }
- lastEnd = n->pos() + n->length();
- Note newNote(*n);
- newNote.setPos(n->pos(newPattern->startPosition()));
- newPattern->addNote(newNote, false);
- }
- delete p;
- p = nullptr;
- }
- };
- bool MidiImport::readSMF( TrackContainer* tc )
- {
- const int MIDI_CC_COUNT = 128 + 1; // 0-127 (128) + pitch bend
- const int preTrackSteps = 2;
- QProgressDialog pd( TrackContainer::tr( "Importing MIDI-file..." ),
- TrackContainer::tr( "Cancel" ), 0, preTrackSteps, gui->mainWindow() );
- pd.setWindowTitle( TrackContainer::tr( "Please wait..." ) );
- pd.setWindowModality(Qt::WindowModal);
- pd.setMinimumDuration( 0 );
- pd.setValue( 0 );
- std::istringstream stream(readAllData().toStdString());
- Alg_seq_ptr seq = new Alg_seq(stream, true);
- seq->convert_to_beats();
- pd.setMaximum( seq->tracks() + preTrackSteps );
- pd.setValue( 1 );
-
- // 128 CC + Pitch Bend
- smfMidiCC ccs[MIDI_CC_COUNT];
- // channels can be set out of 256 range
- // using unordered_map should fix most invalid loads and crashes while loading
- std::unordered_map<long, smfMidiChannel> chs;
- // NOTE: unordered_map::operator[] creates a new element if none exists
- MeterModel & timeSigMM = Engine::getSong()->getTimeSigModel();
- AutomationTrack * nt = dynamic_cast<AutomationTrack*>(
- Track::create(Track::AutomationTrack, Engine::getSong()));
- nt->setName(tr("MIDI Time Signature Numerator"));
- AutomationTrack * dt = dynamic_cast<AutomationTrack*>(
- Track::create(Track::AutomationTrack, Engine::getSong()));
- dt->setName(tr("MIDI Time Signature Denominator"));
- AutomationPattern * timeSigNumeratorPat =
- new AutomationPattern(nt);
- timeSigNumeratorPat->setDisplayName(tr("Numerator"));
- timeSigNumeratorPat->addObject(&timeSigMM.numeratorModel());
- AutomationPattern * timeSigDenominatorPat =
- new AutomationPattern(dt);
- timeSigDenominatorPat->setDisplayName(tr("Denominator"));
- timeSigDenominatorPat->addObject(&timeSigMM.denominatorModel());
-
- // TODO: adjust these to Time.Sig changes
- double beatsPerBar = 4;
- double ticksPerBeat = DefaultTicksPerBar / beatsPerBar;
- // Time-sig changes
- Alg_time_sigs * timeSigs = &seq->time_sig;
- for( int s = 0; s < timeSigs->length(); ++s )
- {
- Alg_time_sig timeSig = (*timeSigs)[s];
- timeSigNumeratorPat->putValue(timeSig.beat * ticksPerBeat, timeSig.num);
- timeSigDenominatorPat->putValue(timeSig.beat * ticksPerBeat, timeSig.den);
- }
- // manually call otherwise the pattern shows being 1 bar
- timeSigNumeratorPat->updateLength();
- timeSigDenominatorPat->updateLength();
- 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 );
- }
- }
- // Update the tempo to avoid crash when playing a project imported
- // via the command line
- Engine::updateFramesPerTick();
- // 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 )
- {
- QString trackName = QString( tr( "Track" ) + " %1" ).arg( t );
- Alg_track_ptr trk = seq->track( t );
- pd.setValue( t + preTrackSteps );
- for( int c = 0; c < MIDI_CC_COUNT; 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 )
- {
- bool handled = false;
- if( evt->is_update() )
- {
- QString attr = evt->get_attribute();
- // seqnames is a track0 identifier (see allegro code)
- if (attr == (t == 0 ? "seqnames" : "tracknames")
- && evt->get_update_type() == 's')
- {
- trackName = evt->get_string_value();
- handled = true;
- }
- }
- if( !handled ) {
- // Write debug output
- printf("MISSING GLOBAL HANDLER\n");
- printf(" Chn: %d, Type Code: %d, Time: %f", (int) evt->chan,
- evt->get_type_code(), evt->time );
- if ( evt->is_update() )
- {
- printf( ", Update Type: %s", evt->get_attribute() );
- if ( evt->get_update_type() == 'a' )
- {
- printf( ", Atom: %s", evt->get_atom_value() );
- }
- }
- printf( "\n" );
- }
- }
- else if (evt->is_note())
- {
- smfMidiChannel * ch = chs[evt->chan].create( tc, trackName );
- Alg_note_ptr noteEvt = dynamic_cast<Alg_note_ptr>( evt );
- int ticks = noteEvt->get_duration() * ticksPerBeat;
- Note n( (ticks < 1 ? 1 : ticks ),
- noteEvt->get_start_time() * ticksPerBeat,
- noteEvt->get_identifier() - 12,
- noteEvt->get_loud() * (200.f / 127.f)); // Map from MIDI velocity to LMMS volume
- ch->addNote( n );
-
- }
-
- else if( evt->is_update() )
- {
- smfMidiChannel * ch = chs[evt->chan].create( tc, trackName );
- 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.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;
- default:
- //TODO: something useful for other CCs
- break;
- }
- if( objModel )
- {
- if( time == 0 && objModel )
- {
- objModel->setInitValue( cc );
- }
- else
- {
- if( ccs[ccid].at == NULL ) {
- ccs[ccid].create( tc, trackName + " > " + (
- objModel != NULL ?
- objModel->displayName() :
- QString("CC %1").arg(ccid) ) );
- }
- 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( auto& c: chs )
- {
- if (c.second.hasNotes)
- {
- c.second.splitPatterns();
- }
- else if (c.second.it)
- {
- printf(" Should remove empty track\n");
- // must delete trackView first - but where is it?
- //tc->removeTrack( chs[c].it );
- //it->deleteLater();
- }
- // Set channel 10 to drums as per General MIDI's orders
- if (c.first % 16l == 9 /* channel 10 */
- && c.second.hasNotes && c.second.it_inst && c.second.isSF2)
- {
- c.second.it_inst->childModel("bank")->setValue(128);
- c.second.it_inst->childModel("patch")->setValue(0);
- }
- }
- 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_EXPORT Plugin * lmms_plugin_main( Model *, void * _data )
- {
- return new MidiImport( QString::fromUtf8(
- static_cast<const char *>( _data ) ) );
- }
- }
|