summaryrefslogtreecommitdiff
path: root/muse2/muse/exportmidi.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'muse2/muse/exportmidi.cpp')
-rw-r--r--muse2/muse/exportmidi.cpp394
1 files changed, 261 insertions, 133 deletions
diff --git a/muse2/muse/exportmidi.cpp b/muse2/muse/exportmidi.cpp
index 8f5af4c5..8e10884c 100644
--- a/muse2/muse/exportmidi.cpp
+++ b/muse2/muse/exportmidi.cpp
@@ -4,6 +4,7 @@
// $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
@@ -23,9 +24,9 @@
#include <stdio.h>
#include <QString>
+#include <QMessageBox>
#include "al/sig.h" // Tim.
-
#include "app.h"
#include "midifile.h"
#include "midi.h"
@@ -83,12 +84,13 @@ static void addController(MPEventList* l, int tick, int port, int channel, int a
int lb = (b >> 8) & 0xff;
int pr = b & 0x7f;
int tickoffset = 0;
- switch(MusEGlobal::song->mtype()) {
- case MT_GM: // no HBANK/LBANK
- break;
- case MT_GS:
- case MT_XG:
- case MT_UNKNOWN:
+ // REMOVE Tim. Song type removal.
+ //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;
@@ -97,8 +99,8 @@ static void addController(MPEventList* l, int tick, int port, int channel, int a
l->add(MidiPlayEvent(tick+tickoffset, port, channel, ME_CONTROLLER, CTRL_LBANK, lb));
++tickoffset;
}
- break;
- }
+ // break;
+ // }
l->add(MidiPlayEvent(tick+tickoffset, port, channel, ME_PROGRAM, pr, 0));
}
else if (a < CTRL_NRPN14_OFFSET) { // RPN14 Controller
@@ -123,6 +125,109 @@ static void addController(MPEventList* l, int tick, int port, int channel, int a
}
}
+//---------------------------------------------------------
+// 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(MusECore::EventList* evlist, MusECore::MPEventList* mpevlist, MusECore::MidiTrack* track, MusECore::Part* part, int port, int channel)
+{
+ for (MusECore::iEvent i = evlist->begin(); i != evlist->end(); ++i)
+ {
+ 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::PAfter:
+ mpevlist->add(MusECore::MidiPlayEvent(tick, port, channel, MusECore::ME_AFTERTOUCH, ev.dataA(), ev.dataB()));
+ break;
+
+ case MusECore::CAfter:
+ mpevlist->add(MusECore::MidiPlayEvent(tick, port, channel, MusECore::ME_POLYAFTER, ev.dataA(), ev.dataB()));
+ 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 {
@@ -133,6 +238,34 @@ namespace MusEGui {
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,
@@ -144,6 +277,8 @@ void MusE::exportMidi()
MusECore::TrackList* tl = MusEGlobal::song->tracks(); // Changed to full track list so user can rearrange tracks.
MusECore::MidiFileTrackList* mtl = new MusECore::MidiFileTrackList;
+ std::set<int> used_ports;
+
int i = 0;
MusECore::MidiFileTrack* mft = 0;
for (MusECore::ciTrack im = tl->begin(); im != tl->end(); ++im) {
@@ -153,7 +288,7 @@ void MusE::exportMidi()
MusECore::MidiTrack* track = (MusECore::MidiTrack*)(*im);
- if (i == 0 || (i != 0 && MusEGlobal::config.smfFormat != 0)) // Changed to single track. Tim
+ if (i == 0 || MusEGlobal::config.smfFormat != 0) // Changed to single track. Tim
{
mft = new MusECore::MidiFileTrack;
mtl->push_back(mft);
@@ -162,14 +297,13 @@ void MusE::exportMidi()
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
- // - GM/GS/XG Initialization
//---------------------------------------------------
if (i == 0) {
@@ -181,8 +315,8 @@ void MusE::exportMidi()
QByteArray ba = m->second.name().toLatin1();
const char* name = ba.constData();
int len = ba.length();
- MusECore::MidiPlayEvent ev(m->first, port, MusECore::ME_META, (unsigned char*)name, len);
- ev.setA(0x6);
+ 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);
}
@@ -193,8 +327,8 @@ void MusE::exportMidi()
const char* copyright = ba.constData();
if (copyright && *copyright) {
int len = ba.length();
- MusECore::MidiPlayEvent ev(0, port, MusECore::ME_META, (unsigned char*)copyright, len);
- ev.setA(0x2);
+ MusECore::MidiPlayEvent ev(0, port, MusECore::ME_META, (const unsigned char*)copyright, len);
+ ev.setA(MusECore::ME_META_TEXT_2_COPYRIGHT);
l->add(ev);
}
@@ -203,36 +337,21 @@ void MusE::exportMidi()
//
//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()) {
- int len = comment.length();
- MusECore::MidiPlayEvent ev(0, port, MusECore::ME_META, (const unsigned char*)(comment.toLatin1().constData()), len);
- ev.setA(0x1);
+ //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 Songtype SYSEX: GM/GS/XG
- //
-
- switch(MusEGlobal::song->mtype()) {
- case MT_GM:
- l->add(MusECore::MidiPlayEvent(0, port, MusECore::ME_SYSEX, MusECore::gmOnMsg, MusECore::gmOnMsgLen));
- break;
- case MT_GS:
- l->add(MusECore::MidiPlayEvent(0, port, MusECore::ME_SYSEX, MusECore::gmOnMsg, MusECore::gmOnMsgLen));
- l->add(MusECore::MidiPlayEvent(250, port, MusECore::ME_SYSEX, MusECore::gsOnMsg, MusECore::gsOnMsgLen));
- break;
- case MT_XG:
- l->add(MusECore::MidiPlayEvent(0, port, MusECore::ME_SYSEX, MusECore::gmOnMsg, MusECore::gmOnMsgLen));
- l->add(MusECore::MidiPlayEvent(250, port, MusECore::ME_SYSEX, MusECore::xgOnMsg, MusECore::xgOnMsgLen));
- break;
- case MT_UNKNOWN:
- break;
- }
-
- //---------------------------------------------------
// Write Tempomap
//
MusECore::TempoList* tl = &MusEGlobal::tempomap;
@@ -244,7 +363,7 @@ void MusE::exportMidi()
data[1] = (tempo >> 8) & 0xff;
data[0] = (tempo >> 16) & 0xff;
MusECore::MidiPlayEvent ev(event->tick, port, MusECore::ME_META, data, 3);
- ev.setA(0x51);
+ ev.setA(MusECore::ME_META_SET_TEMPO);
l->add(ev);
}
@@ -279,7 +398,7 @@ void MusE::exportMidi()
MusECore::MidiPlayEvent ev(event->tick, port, MusECore::ME_META, data, sz);
- ev.setA(0x58);
+ ev.setA(MusECore::ME_META_TIME_SIGNATURE);
l->add(ev);
}
}
@@ -288,14 +407,15 @@ void MusE::exportMidi()
// track name
//-----------------------------------
- if (i == 0 || (i != 0 && MusEGlobal::config.smfFormat != 0))
+ 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, (unsigned char*)name, len+1);
- ev.setA(0x3); // Meta Sequence/Track Name
+ 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);
}
}
@@ -310,104 +430,112 @@ void MusE::exportMidi()
QByteArray ba = track->comment().toLatin1();
const char* comment = ba.constData();
int len = ba.length();
- MusECore::MidiPlayEvent ev(0, port, MusECore::ME_META, (unsigned char*)comment, len+1);
- ev.setA(0xf); // Meta Text
+ 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<int>::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::EventList* evlist = part->events();
- for (MusECore::iEvent i = evlist->begin(); i != evlist->end(); ++i) {
- MusECore::Event ev = i->second;
- int tick = ev.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->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->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;
- l->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
- l->add(MusECore::MidiPlayEvent(tick+len, port, channel, MusECore::ME_NOTEON, pitch, 0));
- else
- l->add(MusECore::MidiPlayEvent(tick+len, port, channel, MusECore::ME_NOTEOFF, pitch, velo));
- }
- break;
-
- case MusECore::Controller:
- addController(l, tick, port, channel, ev.dataA(), ev.dataB());
- break;
-
- case MusECore::Sysex:
- l->add(MusECore::MidiPlayEvent(tick, port, MusECore::ME_SYSEX, ev.eventData()));
- break;
-
- case MusECore::PAfter:
- l->add(MusECore::MidiPlayEvent(tick, port, channel, MusECore::ME_AFTERTOUCH, ev.dataA(), ev.dataB()));
- break;
-
- case MusECore::CAfter:
- l->add(MusECore::MidiPlayEvent(tick, port, channel, MusECore::ME_POLYAFTER, ev.dataA(), ev.dataB()));
- break;
-
- case MusECore::Meta:
- {
- MusECore::MidiPlayEvent mpev(tick, port, MusECore::ME_META, ev.eventData());
- mpev.setA(ev.dataA());
- l->add(mpev);
- }
- break;
- case MusECore::Wave:
- break;
- }
- }
+ MusECore::addEventList(evlist, l, track, part, port, channel);
}
- ++i;
-
+
+ ++i;
}
+
mf.setDivision(MusEGlobal::config.midiDivision);
- mf.setMType(MusEGlobal::song->mtype());
mf.setTrackList(mtl, i);
mf.write();