diff options
Diffstat (limited to 'muse_qt4_evolution/muse/importmidi.cpp')
-rw-r--r-- | muse_qt4_evolution/muse/importmidi.cpp | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/muse_qt4_evolution/muse/importmidi.cpp b/muse_qt4_evolution/muse/importmidi.cpp new file mode 100644 index 00000000..0bd33df8 --- /dev/null +++ b/muse_qt4_evolution/muse/importmidi.cpp @@ -0,0 +1,589 @@ +//============================================================================= +// MusE +// Linux Music Editor +// $Id:$ +// +// Copyright (C) 2002-2006 by Werner Schweer and others +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2. +// +// 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; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +//============================================================================= + +#include "globals.h" +#include "muse.h" +#include "song.h" +#include "widgets/filedialog.h" +#include "midi.h" +#include "midifile.h" +#include "transport.h" +#include "midiedit/drummap.h" +#include "al/sig.h" +#include "al/tempo.h" +#include "instruments/minstrument.h" +#include "gconfig.h" +#include "part.h" +#include "importmidi.h" +#include "projectdialog.h" +#include "templatedialog.h" +#include "audio.h" +#include "mixer.h" +#include "arranger.h" +#include "midictrl.h" +#include "midiinport.h" +#include "midioutport.h" + +//--------------------------------------------------------- +// ImportMidiDialog +//--------------------------------------------------------- + +ImportMidiDialog::ImportMidiDialog(QWidget* parent) + : QDialog(parent) + { + setupUi(this); + bg = new QButtonGroup(this); + bg->setExclusive(true); + bg->addButton(addToProject, 0); + bg->addButton(createNewProject, 1); + createNewProject->setChecked(true); + connect(selectTemplate,SIGNAL(clicked()), SLOT(selectTemplateClicked())); + connect(selectProject, SIGNAL(clicked()), SLOT(selectProjectClicked())); + } + +//--------------------------------------------------------- +// selectTemplateClicked +//--------------------------------------------------------- + +void ImportMidiDialog::selectTemplateClicked() + { + TemplateDialog templateDialog; + templateDialog.setTemplatePath(templateName->text()); + int rv = templateDialog.exec(); + if (rv == 0) + return; + templateName->setText(templateDialog.templatePath()); + } + +//--------------------------------------------------------- +// selectProjectClicked +//--------------------------------------------------------- + +void ImportMidiDialog::selectProjectClicked() + { + ProjectDialog projectDialog; + projectDialog.setProjectName(projectName->text()); + int rv = projectDialog.exec(); + if (rv == 0) + return; + projectName->setText(projectDialog.projectName()); + } + +//--------------------------------------------------------- +// setProjectName +//--------------------------------------------------------- + +void ImportMidiDialog::setProjectName(const QString& name) + { + projectName->setText(name); + } + +//--------------------------------------------------------- +// setTemplateName +//--------------------------------------------------------- + +void ImportMidiDialog::setTemplateName(const QString& name) + { + templateName->setText(name); + } + +//--------------------------------------------------------- +// importMidi +//--------------------------------------------------------- + +void MusE::importMidi() + { + importMidi(QString()); + } + +void MusE::importMidi(const QString &file) + { + QString fn; + QStringList pattern; + + const char** s = midi_file_pattern; + while (*s) + pattern << *s++; + + if (file.isEmpty()) { + fn = getOpenFileName(lastMidiPath, pattern, this, + tr("MusE: Import Midi")); + lastMidiPath = fn; + if (fn.isEmpty()) + return; + } + else + fn = file; + + QFileInfo fi(fn); + + ImportMidiDialog mid(this); + mid.setProjectName(fi.baseName()); + mid.setTemplateName(""); + + if (mid.exec() == 0) + return; + + if (mid.doCreateNewProject()) { + QString header = tr("MusE: import midi file"); + QString path(mid.projectName->text()); + QDir pd(QDir::homePath() + "/" + config.projectPath + "/" + path); + + if (leaveProject()) + return; + if (!pd.mkdir(pd.path())) { + QString s(tr("Cannot create project folder <%1>")); + QMessageBox::critical(this, header, s.arg(pd.path())); + return; + } + if (audio->isPlaying()) { + audio->msgPlay(false); + while (audio->isPlaying()) + qApp->processEvents(); + } + addProject(path); // add to history + seqStop(); + if (mixer1) + mixer1->clear(); + if (mixer2) + mixer2->clear(); + //=========================================================== + // + // close all toplevel windows + // + foreach(QWidget* w, QApplication::topLevelWidgets()) { + if (!w->isVisible()) + continue; + if (strcmp("DrumEdit", w->metaObject()->className()) == 0) + w->close(); + else if (strcmp("PianoRoll", w->metaObject()->className()) == 0) + w->close(); + else if (strcmp("MasterEdit", w->metaObject()->className()) == 0) + w->close(); + else if (strcmp("WaveEdit", w->metaObject()->className()) == 0) + w->close(); + else if (strcmp("ListEdit", w->metaObject()->className()) == 0) + w->close(); + } + emit startLoadSong(); + song->setProjectPath(path); + song->clear(false); + song->setCreated(true); + + QString s = mid.templateName->text(); + bool rv = true; + if (!s.isEmpty()) { + QFile f(s); + if (f.open(QIODevice::ReadOnly)) { + rv = song->read(&f); + f.close(); + } + else { + QString msg(tr("Cannot open template file\n%1")); + QMessageBox::critical(this, header, msg.arg(s)); + } + } + if (!rv) { + QString msg(tr("File <%1> read error")); + QMessageBox::critical(this, header, msg.arg(s)); + } + addMidiFile(fn); + + tr_id->setChecked(config.transportVisible); + bt_id->setChecked(config.bigTimeVisible); + + // + // dont emit song->update(): + song->blockSignals(true); + + showBigtime(config.bigTimeVisible); + showMixer1(config.mixer1Visible); + showMixer2(config.mixer2Visible); + if (mixer1 && config.mixer1Visible) + mixer1->setUpdateMixer(); + if (mixer2 && config.mixer2Visible) + mixer2->setUpdateMixer(); + + if (config.transportVisible) + transport->show(); + transport->move(config.geometryTransport.topLeft()); + showTransport(config.transportVisible); + + song->blockSignals(false); + + transport->setMasterFlag(song->masterFlag()); + punchinAction->setChecked(song->punchin()); + punchoutAction->setChecked(song->punchout()); + loopAction->setChecked(song->loop()); + clipboardChanged(); // enable/disable "Paste" + song->setLen(song->len()); // emit song->lenChanged() signal + + // + // add connected channels + // + TrackList* tl = song->tracks(); +#if 0 + MidiChannelList* mcl = song->midiChannel(); + for (iMidiChannel i = mcl->begin(); i != mcl->end(); ++i) { + MidiChannel* mc = (MidiChannel*)*i; + if (mc->noInRoute() || song->trackExists(mc)) + continue; + tl->push_back(mc); + } +#endif + + selectionChanged(); // enable/disable "Copy" & "Paste" + arranger->endLoadSong(); + song->updatePos(); + // + // send "cur" controller values to devices + // + + for (iTrack i = tl->begin(); i != tl->end(); ++i) { + Track* track = *i; + track->blockSignals(true); + CtrlList* cl = track->controller(); + for (iCtrl ic = cl->begin(); ic != cl->end(); ++ic) { + Ctrl* ctrl = ic->second; + if (ctrl->type() & Ctrl::INT) { + CVal val; + val = ctrl->curVal(); + ctrl->setCurVal(CTRL_VAL_UNKNOWN); + song->setControllerVal(track, ctrl, val); + } + } + track->blockSignals(false); + } + setWindowTitle(QString("MusE: Song: ") + path); + seqStart(); + audio->msgSeek(song->cPos()); + } + else { + // add to project + addMidiFile(fn); + song->update(-1); + } + } + +//--------------------------------------------------------- +// addMidiFile +//--------------------------------------------------------- + +void MusE::addMidiFile(const QString name) + { + QFile* fp = fileOpen(this, name, QString(".mid"), QIODevice::ReadOnly); + if (fp == 0) + return; + MidiFile mf; + bool rv = mf.read(fp); + fp->close(); + delete fp; + + if (rv) { + QString s(tr("reading midifile\n ")); + s += name; + s += tr("\nfailed: "); + s += mf.error(); + QMessageBox::critical(this, QString("MusE"), s); + return; + } + MidiFileTrackList* etl = mf.trackList(); + int division = mf.division(); + + MidiOutPort* outPort = 0; + + if (song->midiOutPorts()->empty()) { + outPort = new MidiOutPort(); + outPort->setDefaultName(); + song->insertTrack0(outPort, -1); + + // + // set preferred instrument + // + MidiInstrument* instr = 0; // genericMidiInstrument; + for (iMidiInstrument mi = midiInstruments.begin(); mi != midiInstruments.end(); ++mi) { + if ((*mi)->iname() == config.defaultMidiInstrument) { + instr = *mi; + break; + } + } + + // + // if midi file is GM/GS/XG this overrides the preferred + // instrument setting + + if (mf.midiType() != MT_GENERIC) { + MidiInstrument* instr2 = 0; + for (iMidiInstrument i = midiInstruments.begin(); i != midiInstruments.end(); ++i) { + MidiInstrument* mi = *i; + switch(mf.midiType()) { + case MT_GM: + if (mi->iname() == "GM") + instr2 = mi; + break; + case MT_GS: + if (mi->iname() == "GS") + instr2 = mi; + break; + case MT_XG: + if (mi->iname() == "XG") + instr2 = mi; + break; + case MT_GENERIC: // cannot happen + break; + } + if (instr2) + break; + } + if (instr2) + instr = instr2; + } + if (instr == 0) + instr = genericMidiInstrument; + outPort->setInstrument(instr); + } + else + outPort = song->midiOutPorts()->front(); + + // + // create MidiTrack and copy events to ->events() + // - combine note on/off events + // - calculate tick value for internal resolution + // + + foreach(const MidiFileTrack* t, *etl) { + const MidiEventList& el = t->events; + if (el.empty()) + continue; + // + // if we split the track, SYSEX and META events go into + // the first target track + + bool first = true; + for (int channel = 0; channel < MIDI_CHANNELS; ++channel) { + // + // check if there are any events for channel in track: + // + iMidiEvent i; + for (i = el.begin(); i != el.end(); ++i) { + MidiEvent ev = *i; + if (ev.type() != ME_SYSEX && ev.type() != ME_META && ev.channel() == channel) + break; + } + if (i == el.end()) + continue; + + MidiTrack* track = new MidiTrack(); + if (t->isDrumTrack) + track->setUseDrumMap(true); +//TODOB track->outRoutes()->push_back(Route(outPort->channel(channel), -1, Route::TRACK)); +// if (inPort && config.connectToAllMidiTracks) { +// for (int ch = 0; ch < MIDI_CHANNELS; ++ch) { +// Route src(inPort, ch, Route::TRACK); +// track->inRoutes()->push_back(src); +// } +// } + + EventList* mel = track->events(); + buildMidiEventList(mel, &el, track, channel, division, first); + first = false; + + for (iEvent i = mel->begin(); i != mel->end(); ++i) { + Event event = i->second; + if (event.type() == Controller) { + int ctrl = event.dataA(); + MidiInstrument* instr = outPort->instrument(); + track->addMidiController(instr, ctrl); + CVal val; + val.i = event.dataB(); + track->addControllerVal(ctrl, event.tick(), val); + } + } + processTrack(track); + if (track->name().isEmpty()) + track->setDefaultName(); + song->insertTrack0(track, -1); + } + if (first) { + // + // track does only contain non-channel messages + // (SYSEX or META) + // + MidiTrack* track = new MidiTrack(); + EventList* mel = track->events(); + buildMidiEventList(mel, &el, track, 0, division, true); + processTrack(track); + if (track->name().isEmpty()) + track->setDefaultName(); + song->insertTrack0(track, -1); + } + } + + TrackList* tl = song->tracks(); + if (!tl->empty()) { + Track* track = tl->front(); + track->setSelected(true); + } + unsigned int l = 1; + MidiTrackList* mtl = song->midis(); + for (iMidiTrack t = mtl->begin(); t != mtl->end(); ++t) { + MidiTrack* track = *t; + PartList* parts = track->parts(); + for (iPart p = parts->begin(); p != parts->end(); ++p) { + unsigned last = p->second->tick() + p->second->lenTick(); + if (last > l) + l = last; + } + } + song->setLen(l); + AL::TimeSignature sig = AL::sigmap.timesig(0); + int z = sig.z; + int n = sig.n; + + transport->setTimesig(z, n); +// int tempo = AL::tempomap.tempo(0); +// transport->setTempo(tempo); + + bool masterF = !AL::tempomap.empty(); + song->setMasterFlag(masterF); + transport->setMasterFlag(masterF); + + song->updatePos(); + } + +//--------------------------------------------------------- +// processTrack +// divide events into parts +//--------------------------------------------------------- + +void MusE::processTrack(MidiTrack* track) + { + EventList* tevents = track->events(); + if (tevents->empty()) + return; + + //--------------------------------------------------- + // create parts + // Break midi tracks into parts. + // A new part is created when a gap of at least + // one measure is detected. Part len is aligned + // to one measure. + //--------------------------------------------------- + + PartList* pl = track->parts(); + + int lastTick = 0; + for (iEvent i = tevents->begin(); i != tevents->end(); ++i) { + Event event = i->second; + int epos = event.tick() + event.lenTick(); + if (epos > lastTick) + lastTick = epos; + } + + int len = song->roundUpBar(lastTick+1); + int bar2, beat; + unsigned tick; + AL::sigmap.tickValues(len, &bar2, &beat, &tick); + + QString partname = track->name(); + + int lastOff = 0; + int st = -1; // start tick current part + int x1 = 0; // start tick current measure + int x2 = 0; // end tick current measure + + for (int bar = 0; bar < bar2; ++bar, x1 = x2) { + x2 = AL::sigmap.bar2tick(bar+1, 0, 0); + if (lastOff > x2) { + // this measure is busy! + continue; + } + iEvent i1 = tevents->lower_bound(x1); + iEvent i2 = tevents->lower_bound(x2); + + if (i1 == i2) { // empty? + if (st != -1) { + Part* part = new Part(track); + part->ref(); + part->setType(AL::TICKS); + part->setTick(st); + part->setLenTick(x1-st); + part->setName(partname); + pl->add(part); + st = -1; + } + } + else { + if (st == -1) + st = x1; // begin new part + //HACK: + //lastOff: + for (iEvent i = i1; i != i2; ++i) { + Event event = i->second; + if (event.type() == Note) { + int off = event.tick() + event.lenTick(); + if (off > lastOff) + lastOff = off; + } + } + } + } + if (st != -1) { + Part* part = new Part(track); + part->ref(); + part->setType(AL::TICKS); + part->setTick(st); + part->setLenTick(x2-st); + part->setName(partname); + pl->add(part); + } + + //------------------------------------------------------------- + // assign events to parts + //------------------------------------------------------------- + + for (iPart p = pl->begin(); p != pl->end(); ++p) { + Part* part = p->second; + int stick = part->tick(); + int etick = part->tick() + part->lenTick(); + iEvent r1 = tevents->lower_bound(stick); + iEvent r2 = tevents->lower_bound(etick); + int startTick = part->tick(); + + EventList* el = part->events(); + for (iEvent i = r1; i != r2; ++i) { + Event ev = i->second; + int ntick = ev.tick() - startTick; + ev.setTick(ntick); + el->add(ev, ntick); + } + tevents->erase(r1, r2); + } + + if (tevents->size()) + printf("-----------events left: %zd\n", tevents->size()); + for (iEvent i = tevents->begin(); i != tevents->end(); ++i) { + printf("%d===\n", i->first); + i->second.dump(); + } + // all events should be processed: + assert(tevents->empty()); + } + |