//========================================================= // MusE // Linux Music Editor // $Id: exportmidi.cpp,v 1.9.2.1 2009/04/01 01:37:10 terminator356 Exp $ // // (C) Copyright 1999-2003 Werner Schweer (ws@seh.de) // (C) Copyright 2012 Tim E. Real (terminator356 on users dot sourceforge dot 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; 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; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //========================================================= #include #include #include #include "al/sig.h" // Tim. #include "app.h" #include "midifile.h" #include "midi.h" #include "midictrl.h" #include "globals.h" #include "filedialog.h" #include "track.h" #include "song.h" #include "mpevent.h" #include "event.h" #include "marker/marker.h" #include "drummap.h" #include "gconfig.h" namespace MusECore { //--------------------------------------------------------- // addController //--------------------------------------------------------- static void addController(MPEventList* l, int tick, int port, int channel, int a, int b) { if (a < CTRL_14_OFFSET) { // 7 Bit Controller l->add(MidiPlayEvent(tick, port, channel, ME_CONTROLLER, a, b)); } else if (a < CTRL_RPN_OFFSET) { // 14 Bit Controller int ctrlH = (a >> 8) & 0x7f; int ctrlL = a & 0x7f; int dataH = (b >> 7) & 0x7f; int dataL = b & 0x7f; l->add(MidiPlayEvent(tick, port, channel, ME_CONTROLLER, ctrlH, dataH)); l->add(MidiPlayEvent(tick+1, port, channel, ME_CONTROLLER, ctrlL, dataL)); } else if (a < CTRL_NRPN_OFFSET) { // RPN 7-Bit Controller int ctrlH = (a >> 8) & 0x7f; int ctrlL = a & 0x7f; l->add(MidiPlayEvent(tick, port, channel, ME_CONTROLLER, CTRL_HRPN, ctrlH)); l->add(MidiPlayEvent(tick+1, port, channel, ME_CONTROLLER, CTRL_LRPN, ctrlL)); l->add(MidiPlayEvent(tick+2, port, channel, ME_CONTROLLER, CTRL_HDATA, b)); } else if (a < CTRL_INTERNAL_OFFSET) { // NRPN 7-Bit Controller int ctrlH = (a >> 8) & 0x7f; int ctrlL = a & 0x7f; l->add(MidiPlayEvent(tick, port, channel, ME_CONTROLLER, CTRL_HNRPN, ctrlH)); l->add(MidiPlayEvent(tick+1, port, channel, ME_CONTROLLER, CTRL_LNRPN, ctrlL)); l->add(MidiPlayEvent(tick+2, port, channel, ME_CONTROLLER, CTRL_HDATA, b)); } else if (a == CTRL_PITCH) { int a = b + 8192; int b = a >> 7; l->add(MidiPlayEvent(tick, port, channel, ME_PITCHBEND, a & 0x7f, b & 0x7f)); } else if (a == CTRL_PROGRAM) { int hb = (b >> 16) & 0xff; int lb = (b >> 8) & 0xff; int pr = b & 0x7f; int tickoffset = 0; // REMOVE Tim. Song type removal. Hm, TEST is this OK here? //switch(MusEGlobal::song->mtype()) { // case MT_GM: // no HBANK/LBANK // break; // case MT_GS: // case MT_XG: // case MT_UNKNOWN: if (hb != 0xff) { l->add(MidiPlayEvent(tick, port, channel, ME_CONTROLLER, CTRL_HBANK, hb)); ++tickoffset; } if (lb != 0xff) { l->add(MidiPlayEvent(tick+tickoffset, port, channel, ME_CONTROLLER, CTRL_LBANK, lb)); ++tickoffset; } // break; // } l->add(MidiPlayEvent(tick+tickoffset, port, channel, ME_PROGRAM, pr, 0)); } else if (a < CTRL_NRPN14_OFFSET) { // RPN14 Controller int ctrlH = (a >> 8) & 0x7f; int ctrlL = a & 0x7f; int dataH = (b >> 7) & 0x7f; int dataL = b & 0x7f; l->add(MidiPlayEvent(tick, port, channel, ME_CONTROLLER, CTRL_HRPN, ctrlH)); l->add(MidiPlayEvent(tick+1, port, channel, ME_CONTROLLER, CTRL_LRPN, ctrlL)); l->add(MidiPlayEvent(tick+2, port, channel, ME_CONTROLLER, CTRL_HDATA, dataH)); l->add(MidiPlayEvent(tick+3, port, channel, ME_CONTROLLER, CTRL_LDATA, dataL)); } else if (a < CTRL_NONE_OFFSET) { // NRPN14 Controller int ctrlH = (a >> 8) & 0x7f; int ctrlL = a & 0x7f; int dataH = (b >> 7) & 0x7f; int dataL = b & 0x7f; l->add(MidiPlayEvent(tick, port, channel, ME_CONTROLLER, CTRL_HNRPN, ctrlH)); l->add(MidiPlayEvent(tick+1, port, channel, ME_CONTROLLER, CTRL_LNRPN, ctrlL)); l->add(MidiPlayEvent(tick+2, port, channel, ME_CONTROLLER, CTRL_HDATA, dataH)); l->add(MidiPlayEvent(tick+3, port, channel, ME_CONTROLLER, CTRL_LDATA, dataL)); } } //--------------------------------------------------------- // addEventList // part can be NULL meaning no part used. // track can be NULL meaning no concept of drum notes is allowed in init sequences. //--------------------------------------------------------- static void addEventList(const MusECore::EventList& evlist, MusECore::MPEventList* mpevlist, MusECore::MidiTrack* track, MusECore::Part* part, int port, int channel) { for (MusECore::ciEvent i = evlist.begin(); i != evlist.end(); ++i) { const MusECore::Event& ev = i->second; int tick = ev.tick(); if(part) tick += part->tick(); switch (ev.type()) { case MusECore::Note: { if (ev.velo() == 0) { printf("Warning: midi note has velocity 0, (ignored)\n"); continue; } int pitch; if (track && track->type() == MusECore::Track::DRUM) { // Map drum-notes to the drum-map values int instr = ev.pitch(); pitch = MusEGlobal::drumMap[instr].anote; } else pitch = ev.pitch(); int velo = ev.velo(); int len = ev.lenTick(); //--------------------------------------- // apply trackinfo values //--------------------------------------- if (track && (track->transposition || track->velocity || track->compression != 100 || track->len != 100)) { pitch += track->transposition; if (pitch > 127) pitch = 127; if (pitch < 0) pitch = 0; velo += track->velocity; velo = (velo * track->compression) / 100; if (velo > 127) velo = 127; if (velo < 1) // no off event velo = 1; len = (len * track->len) / 100; } if (len <= 0) len = 1; mpevlist->add(MusECore::MidiPlayEvent(tick, port, channel, MusECore::ME_NOTEON, pitch, velo)); if(MusEGlobal::config.expOptimNoteOffs) // Save space by replacing note offs with note on velocity 0 mpevlist->add(MusECore::MidiPlayEvent(tick+len, port, channel, MusECore::ME_NOTEON, pitch, 0)); else mpevlist->add(MusECore::MidiPlayEvent(tick+len, port, channel, MusECore::ME_NOTEOFF, pitch, velo)); } break; case MusECore::Controller: addController(mpevlist, tick, port, channel, ev.dataA(), ev.dataB()); break; case MusECore::Sysex: { mpevlist->add(MusECore::MidiPlayEvent(tick, port, MusECore::ME_SYSEX, ev.eventData())); //MusECore::MidiPlayEvent ev(tick, port, MusECore::ME_SYSEX, ev.eventData()); //ev.setChannel(channel); // Sysex are channelless, but this is required for sorting! //mpevlist->add(ev); } break; case MusECore::Meta: { MusECore::MidiPlayEvent mpev(tick, port, MusECore::ME_META, ev.eventData()); mpev.setA(ev.dataA()); //mpev.setChannel(channel); // Metas are channelless, but this is required for sorting! mpevlist->add(mpev); } break; case MusECore::Wave: break; } } } } // namespace MusECore namespace MusEGui { //--------------------------------------------------------- // exportMidi //--------------------------------------------------------- void MusE::exportMidi() { if(MusEGlobal::config.smfFormat == 0) // Want single track? Warn if multiple ports in song... { MusECore::MidiTrackList* mtl = MusEGlobal::song->midis(); int prev_port = -1; for(MusECore::ciMidiTrack im = mtl->begin(); im != mtl->end(); ++im) { int port = (*im)->outPort(); if(prev_port == -1) { prev_port = port; continue; } if(port != prev_port) { if(QMessageBox::warning(this, tr("MusE: Warning"), tr("The song uses multiple ports but export format 0 (single track) is set.\n" "The first track's port will be used. Playback will likely be wrong\n" " unless the channels used in one port are different from all other ports.\n" "Canceling and setting a different export format would be better.\nContinue?"), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok) != QMessageBox::Ok) return; break; } } } MusEGui::MFile file(QString("midis"), QString(".mid")); FILE* fp = file.open("w", MusEGlobal::midi_file_save_pattern, this, false, true, tr("MusE: Export Midi")); if (fp == 0) return; MusECore::MidiFile mf(fp); MusECore::TrackList* tl = MusEGlobal::song->tracks(); // Changed to full track list so user can rearrange tracks. MusECore::MidiFileTrackList* mtl = new MusECore::MidiFileTrackList; std::set used_ports; int i = 0; MusECore::MidiFileTrack* mft = 0; for (MusECore::ciTrack im = tl->begin(); im != tl->end(); ++im) { if(!(*im)->isMidiTrack()) continue; MusECore::MidiTrack* track = (MusECore::MidiTrack*)(*im); if (i == 0 || MusEGlobal::config.smfFormat != 0) // Changed to single track. Tim { mft = new MusECore::MidiFileTrack; mtl->push_back(mft); } MusECore::MPEventList* l = &(mft->events); int port = track->outPort(); int channel = track->outChannel(); //--------------------------------------------------- // only first midi track contains // - Track Marker // - copyright // - time signature // - tempo map //--------------------------------------------------- if (i == 0) { //--------------------------------------------------- // Write Track Marker // MusECore::MarkerList* ml = MusEGlobal::song->marker(); for (MusECore::ciMarker m = ml->begin(); m != ml->end(); ++m) { QByteArray ba = m->second.name().toLatin1(); const char* name = ba.constData(); int len = ba.length(); MusECore::MidiPlayEvent ev(m->first, port, MusECore::ME_META, (const unsigned char*)name, len); ev.setA(MusECore::ME_META_TEXT_6_MARKER); l->add(ev); } //--------------------------------------------------- // Write Copyright // QByteArray ba = MusEGlobal::config.copyright.toLatin1(); const char* copyright = ba.constData(); if (copyright && *copyright) { int len = ba.length(); MusECore::MidiPlayEvent ev(0, port, MusECore::ME_META, (const unsigned char*)copyright, len); ev.setA(MusECore::ME_META_TEXT_2_COPYRIGHT); l->add(ev); } //--------------------------------------------------- // Write Comment // //if (MusEGlobal::config.smfFormat == 0) // Only for smf 0 added by Tim. FIXME: Is this correct? See below. { //QString comment = track->comment(); //if (!comment.isEmpty()) { if (!track->comment().isEmpty()) { //int len = comment.length(); QByteArray ba = track->comment().toLatin1(); const char* comment = ba.constData(); int len = ba.length(); //MusECore::MidiPlayEvent ev(0, port, MusECore::ME_META, (const unsigned char*)(comment.toLatin1().constData()), len); MusECore::MidiPlayEvent ev(0, port, MusECore::ME_META, (const unsigned char*)comment, len); ev.setA(MusECore::ME_META_TEXT_1_COMMENT); l->add(ev); } } //--------------------------------------------------- // Write Tempomap // MusECore::TempoList* tl = &MusEGlobal::tempomap; for (MusECore::ciTEvent e = tl->begin(); e != tl->end(); ++e) { MusECore::TEvent* event = e->second; unsigned char data[3]; int tempo = event->tempo; data[2] = tempo & 0xff; data[1] = (tempo >> 8) & 0xff; data[0] = (tempo >> 16) & 0xff; MusECore::MidiPlayEvent ev(event->tick, port, MusECore::ME_META, data, 3); ev.setA(MusECore::ME_META_SET_TEMPO); l->add(ev); } //--------------------------------------------------- // Write Signatures // const AL::SigList* sl = &AL::sigmap; for (AL::ciSigEvent e = sl->begin(); e != sl->end(); ++e) { AL::SigEvent* event = e->second; int sz = (MusEGlobal::config.exp2ByteTimeSigs ? 2 : 4); // export 2 byte timesigs instead of 4 ? unsigned char data[sz]; data[0] = event->sig.z; switch(event->sig.n) { case 1: data[1] = 0; break; case 2: data[1] = 1; break; case 4: data[1] = 2; break; case 8: data[1] = 3; break; case 16: data[1] = 4; break; case 32: data[1] = 5; break; case 64: data[1] = 6; break; default: fprintf(stderr, "wrong Signature; denominator is %d\n", event->sig.n); break; } // By T356. In muse the metronome pulse is fixed at 24 (once per quarter-note). // The number of 32nd notes per 24 MIDI clock signals (per quarter-note) is 8. if(!MusEGlobal::config.exp2ByteTimeSigs) { data[2] = 24; data[3] = 8; } MusECore::MidiPlayEvent ev(event->tick, port, MusECore::ME_META, data, sz); ev.setA(MusECore::ME_META_TIME_SIGNATURE); l->add(ev); } } //----------------------------------- // track name //----------------------------------- if (i == 0 || MusEGlobal::config.smfFormat != 0) { if (!track->name().isEmpty()) { QByteArray ba = track->name().toLatin1(); const char* name = ba.constData(); int len = ba.length(); MusECore::MidiPlayEvent ev(0, port, MusECore::ME_META, (const unsigned char*)name, len); ev.setA(MusECore::ME_META_TEXT_3_TRACK_NAME); // Meta Sequence/Track Name //ev.setChannel(channel); // Metas are channelless, but this is required for sorting! l->add(ev); } } //----------------------------------- // track comment //----------------------------------- if (MusEGlobal::config.smfFormat != 0) { if (!track->comment().isEmpty()) { QByteArray ba = track->comment().toLatin1(); const char* comment = ba.constData(); int len = ba.length(); MusECore::MidiPlayEvent ev(0, port, MusECore::ME_META, (const unsigned char*)comment, len); ev.setA(MusECore::ME_META_TEXT_F_TRACK_COMMENT); // Meta Text //ev.setChannel(channel); // Metas are channelless, but this is required for sorting! l->add(ev); } } //----------------------------------------- // Write device name or port change meta //----------------------------------------- if((i == 0 && MusEGlobal::config.exportPortDeviceSMF0) || (MusEGlobal::config.smfFormat != 0)) { if(port >= 0 && port < MIDI_PORTS) { if(MusEGlobal::config.exportPortsDevices == MusEGlobal::EXPORT_PORTS_DEVICES_ALL || MusEGlobal::config.exportPortsDevices == MusEGlobal::PORT_NUM_META) { unsigned char port_char = (unsigned char)port; MusECore::MidiPlayEvent ev(0, port, MusECore::ME_META, &port_char, 1); ev.setA(MusECore::ME_META_PORT_CHANGE); // Meta port change //ev.setChannel(channel); // Metas are channelless, but this is required for sorting! l->add(ev); } if(MusEGlobal::config.exportPortsDevices == MusEGlobal::EXPORT_PORTS_DEVICES_ALL || MusEGlobal::config.exportPortsDevices == MusEGlobal::DEVICE_NAME_META) { MusECore::MidiDevice* dev = MusEGlobal::midiPorts[port].device(); const char* str; int len; QByteArray ba; if(dev && !dev->name().isEmpty()) ba = dev->name().toLatin1(); else ba = QString::number(port).toLatin1(); str = ba.constData(); len = ba.length(); MusECore::MidiPlayEvent ev(0, port, MusECore::ME_META, (const unsigned char*)str, len); ev.setA(MusECore::ME_META_TEXT_9_DEVICE_NAME); // Meta Device Name //ev.setChannel(channel); // Metas are channelless, but this is required for sorting! l->add(ev); } } } //--------------------------------------------------- // Write midi port init sequence: GM/GS/XG etc. // and Instrument Name meta. //--------------------------------------------------- std::set::iterator iup = used_ports.find(port); if(iup == used_ports.end()) { if(port >= 0 && port < MIDI_PORTS) { MusECore::MidiInstrument* instr = MusEGlobal::midiPorts[port].instrument(); if(instr) { if(i == 0 || MusEGlobal::config.smfFormat != 0) { //-------------------------- // Port midi init sequence //-------------------------- if(MusEGlobal::config.exportModeInstr == MusEGlobal::EXPORT_MODE_INSTR_ALL || MusEGlobal::config.exportModeInstr == MusEGlobal::MODE_SYSEX) { MusECore::EventList* el = instr->midiInit(); if(!el->empty()) MusECore::addEventList(*el, l, NULL, NULL, port, channel); // No track or part passed for init sequences } //-------------------------- // Instrument Name meta //-------------------------- if(!instr->iname().isEmpty() && (MusEGlobal::config.exportModeInstr == MusEGlobal::EXPORT_MODE_INSTR_ALL || MusEGlobal::config.exportModeInstr == MusEGlobal::INSTRUMENT_NAME_META)) { const char* str; int len; QByteArray ba = instr->iname().toLatin1(); str = ba.constData(); len = ba.length(); MusECore::MidiPlayEvent ev(0, port, MusECore::ME_META, (const unsigned char*)str, len); ev.setA(MusECore::ME_META_TEXT_4_INSTRUMENT_NAME); // Meta Instrument Name //ev.setChannel(channel); // Metas are channelless, but this is required for sorting! l->add(ev); } } } used_ports.insert(port); } } MusECore::PartList* parts = track->parts(); for (MusECore::iPart p = parts->begin(); p != parts->end(); ++p) { MusECore::MidiPart* part = (MusECore::MidiPart*) (p->second); MusECore::addEventList(part->events(), l, track, part, port, channel); } ++i; } mf.setDivision(MusEGlobal::config.midiDivision); mf.setTrackList(mtl, i); mf.write(); } } // namespace MusEGui