Files
lmms/plugins/midi_import/midi_import.cpp
2008-11-21 23:22:36 +00:00

592 lines
12 KiB
C++

/*
* midi_import.cpp - support for importing MIDI-files
*
* Copyright (c) 2005-2008 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* 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 <QtXml/QDomDocument>
#include <QtCore/QDir>
#include <QtGui/QApplication>
#include <QtGui/QProgressDialog>
#include "midi_import.h"
#include "track_container.h"
#include "instrument_track.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 | \
( ( _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 );
}
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<automationTrack *>(
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<automationPattern*>(
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<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()->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<pattern *>( 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();
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<Alg_note_ptr>( 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", (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;
/*
// the curren position is immediately after the "MThd" id
int header_len = readInt( 4 );
if( header_len < 6 )
{
invalid_format:
printf( "midiImport::readSMF(): invalid file format\n" );
return( FALSE );
}
int type = readInt( 2 );
if( type != 0 && type != 1 )
{
printf( "midiImport::readSMF(): type %d format is not "
"supported\n", type );
return( FALSE );
}
int num_tracks = readInt( 2 );
if( num_tracks < 1 || num_tracks > 1000 )
{
printf( "midiImport::readSMF(): invalid number of tracks (%d)\n",
num_tracks );
num_tracks = 0;
return( FALSE );
}
#ifdef LMMS_DEBUG
printf( "tracks: %d\n", num_tracks );
#endif
m_timingDivision = readInt( 2 );
if( m_timingDivision < 0 )
{
goto invalid_format;
}
#ifdef LMMS_DEBUG
printf( "time-division: %d\n", m_timingDivision );
#endif
*/
}
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:
printf( "midiImport::readRIFF(): invalid file format\n" );
return( FALSE );
}
// search for "data" chunk
while( 1 )
{
int id = readID();
int len = read32LE();
if( file().atEnd() )
{
data_not_found:
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 ) );
}
void midiImport::error( void )
{
printf( "midiImport::readTrack(): invalid MIDI data (offset %#x)\n",
(unsigned int) file().pos() );
}
extern "C"
{
// neccessary for getting instance out of shared lib
plugin * PLUGIN_EXPORT lmms_plugin_main( model *, void * _data )
{
return( new midiImport( static_cast<const char *>( _data ) ) );
}
}
#undef pos
#undef setValue