winamp/Src/external_dependencies/openmpt-trunk/soundlib/Load_mid.cpp

1406 lines
37 KiB
C++
Raw Permalink Normal View History

2024-09-24 12:54:57 +00:00
/*
* Load_mid.cpp
* ------------
* Purpose: MIDI file loader
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "Loaders.h"
#include "Dlsbank.h"
#include "MIDIEvents.h"
#ifdef MODPLUG_TRACKER
#include "../mptrack/TrackerSettings.h"
#include "../mptrack/Moddoc.h"
#include "../mptrack/Mptrack.h"
#include "../common/mptFileIO.h"
#endif // MODPLUG_TRACKER
OPENMPT_NAMESPACE_BEGIN
#if defined(MODPLUG_TRACKER) || defined(MPT_FUZZ_TRACKER)
#ifdef LIBOPENMPT_BUILD
struct CDLSBank { static int32 DLSMidiVolumeToLinear(uint32) { return 256; } };
#endif // LIBOPENMPT_BUILD
#define MIDI_DRUMCHANNEL 10
const char *szMidiGroupNames[17] =
{
"Piano",
"Chromatic Percussion",
"Organ",
"Guitar",
"Bass",
"Strings",
"Ensemble",
"Brass",
"Reed",
"Pipe",
"Synth Lead",
"Synth Pad",
"Synth Effects",
"Ethnic",
"Percussive",
"Sound Effects",
"Percussions"
};
const char *szMidiProgramNames[128] =
{
// 1-8: Piano
"Acoustic Grand Piano",
"Bright Acoustic Piano",
"Electric Grand Piano",
"Honky-tonk Piano",
"Electric Piano 1",
"Electric Piano 2",
"Harpsichord",
"Clavi",
// 9-16: Chromatic Percussion
"Celesta",
"Glockenspiel",
"Music Box",
"Vibraphone",
"Marimba",
"Xylophone",
"Tubular Bells",
"Dulcimer",
// 17-24: Organ
"Drawbar Organ",
"Percussive Organ",
"Rock Organ",
"Church Organ",
"Reed Organ",
"Accordion",
"Harmonica",
"Tango Accordion",
// 25-32: Guitar
"Acoustic Guitar (nylon)",
"Acoustic Guitar (steel)",
"Electric Guitar (jazz)",
"Electric Guitar (clean)",
"Electric Guitar (muted)",
"Overdriven Guitar",
"Distortion Guitar",
"Guitar harmonics",
// 33-40 Bass
"Acoustic Bass",
"Electric Bass (finger)",
"Electric Bass (pick)",
"Fretless Bass",
"Slap Bass 1",
"Slap Bass 2",
"Synth Bass 1",
"Synth Bass 2",
// 41-48 Strings
"Violin",
"Viola",
"Cello",
"Contrabass",
"Tremolo Strings",
"Pizzicato Strings",
"Orchestral Harp",
"Timpani",
// 49-56 Ensemble
"String Ensemble 1",
"String Ensemble 2",
"SynthStrings 1",
"SynthStrings 2",
"Choir Aahs",
"Voice Oohs",
"Synth Voice",
"Orchestra Hit",
// 57-64 Brass
"Trumpet",
"Trombone",
"Tuba",
"Muted Trumpet",
"French Horn",
"Brass Section",
"SynthBrass 1",
"SynthBrass 2",
// 65-72 Reed
"Soprano Sax",
"Alto Sax",
"Tenor Sax",
"Baritone Sax",
"Oboe",
"English Horn",
"Bassoon",
"Clarinet",
// 73-80 Pipe
"Piccolo",
"Flute",
"Recorder",
"Pan Flute",
"Blown Bottle",
"Shakuhachi",
"Whistle",
"Ocarina",
// 81-88 Synth Lead
"Lead 1 (square)",
"Lead 2 (sawtooth)",
"Lead 3 (calliope)",
"Lead 4 (chiff)",
"Lead 5 (charang)",
"Lead 6 (voice)",
"Lead 7 (fifths)",
"Lead 8 (bass + lead)",
// 89-96 Synth Pad
"Pad 1 (new age)",
"Pad 2 (warm)",
"Pad 3 (polysynth)",
"Pad 4 (choir)",
"Pad 5 (bowed)",
"Pad 6 (metallic)",
"Pad 7 (halo)",
"Pad 8 (sweep)",
// 97-104 Synth Effects
"FX 1 (rain)",
"FX 2 (soundtrack)",
"FX 3 (crystal)",
"FX 4 (atmosphere)",
"FX 5 (brightness)",
"FX 6 (goblins)",
"FX 7 (echoes)",
"FX 8 (sci-fi)",
// 105-112 Ethnic
"Sitar",
"Banjo",
"Shamisen",
"Koto",
"Kalimba",
"Bag pipe",
"Fiddle",
"Shanai",
// 113-120 Percussive
"Tinkle Bell",
"Agogo",
"Steel Drums",
"Woodblock",
"Taiko Drum",
"Melodic Tom",
"Synth Drum",
"Reverse Cymbal",
// 121-128 Sound Effects
"Guitar Fret Noise",
"Breath Noise",
"Seashore",
"Bird Tweet",
"Telephone Ring",
"Helicopter",
"Applause",
"Gunshot"
};
// Notes 25-85
const char *szMidiPercussionNames[61] =
{
"Seq Click",
"Brush Tap",
"Brush Swirl",
"Brush Slap",
"Brush Swirl W/Attack",
"Snare Roll",
"Castanet",
"Snare Lo",
"Sticks",
"Bass Drum Lo",
"Open Rim Shot",
"Acoustic Bass Drum",
"Bass Drum 1",
"Side Stick",
"Acoustic Snare",
"Hand Clap",
"Electric Snare",
"Low Floor Tom",
"Closed Hi-Hat",
"High Floor Tom",
"Pedal Hi-Hat",
"Low Tom",
"Open Hi-Hat",
"Low-Mid Tom",
"Hi Mid Tom",
"Crash Cymbal 1",
"High Tom",
"Ride Cymbal 1",
"Chinese Cymbal",
"Ride Bell",
"Tambourine",
"Splash Cymbal",
"Cowbell",
"Crash Cymbal 2",
"Vibraslap",
"Ride Cymbal 2",
"Hi Bongo",
"Low Bongo",
"Mute Hi Conga",
"Open Hi Conga",
"Low Conga",
"High Timbale",
"Low Timbale",
"High Agogo",
"Low Agogo",
"Cabasa",
"Maracas",
"Short Whistle",
"Long Whistle",
"Short Guiro",
"Long Guiro",
"Claves",
"Hi Wood Block",
"Low Wood Block",
"Mute Cuica",
"Open Cuica",
"Mute Triangle",
"Open Triangle",
"Shaker",
"Jingle Bell",
"Bell Tree",
};
////////////////////////////////////////////////////////////////////////////////
// Maps a midi instrument - returns the instrument number in the file
uint32 CSoundFile::MapMidiInstrument(uint8 program, uint16 bank, uint8 midiChannel, uint8 note, bool isXG, std::bitset<16> drumChns)
{
ModInstrument *pIns;
program &= 0x7F;
bank &= 0x3FFF;
note &= 0x7F;
// In XG mode, extra drums are on banks with MSB 7F
const bool isDrum = drumChns[midiChannel - 1] || (bank >= 0x3F80 && isXG);
for (uint32 i = 1; i <= m_nInstruments; i++) if (Instruments[i])
{
ModInstrument *p = Instruments[i];
// Drum Kit?
if (isDrum)
{
if (note == p->nMidiDrumKey && bank + 1 == p->wMidiBank) return i;
} else
// Melodic Instrument
{
if (program + 1 == p->nMidiProgram && bank + 1 == p->wMidiBank && p->nMidiDrumKey == 0) return i;
}
}
if(!CanAddMoreInstruments() || !CanAddMoreSamples())
return 0;
pIns = AllocateInstrument(m_nInstruments + 1);
if(pIns == nullptr)
{
return 0;
}
m_nSamples++;
pIns->wMidiBank = bank + 1;
pIns->nMidiProgram = program + 1;
pIns->nFadeOut = 1024;
pIns->nNNA = NewNoteAction::NoteOff;
pIns->nDCT = isDrum ? DuplicateCheckType::Sample : DuplicateCheckType::Note;
pIns->nDNA = DuplicateNoteAction::NoteFade;
if(isDrum)
{
pIns->nMidiChannel = MIDI_DRUMCHANNEL;
pIns->nMidiDrumKey = note;
for(auto &key : pIns->NoteMap)
{
key = NOTE_MIDDLEC;
}
}
pIns->VolEnv.dwFlags.set(ENV_ENABLED);
if (!isDrum) pIns->VolEnv.dwFlags.set(ENV_SUSTAIN);
pIns->VolEnv.reserve(4);
pIns->VolEnv.push_back(EnvelopeNode(0, ENVELOPE_MAX));
pIns->VolEnv.push_back(EnvelopeNode(10, ENVELOPE_MAX));
pIns->VolEnv.push_back(EnvelopeNode(15, (ENVELOPE_MAX + ENVELOPE_MID) / 2));
pIns->VolEnv.push_back(EnvelopeNode(20, ENVELOPE_MIN));
pIns->VolEnv.nSustainStart = pIns->VolEnv.nSustainEnd = 1;
// Set GM program / drum name
if (!isDrum)
{
pIns->name = szMidiProgramNames[program];
} else
{
if (note >= 24 && note <= 84)
pIns->name = szMidiPercussionNames[note - 24];
else
pIns->name = "Percussions";
}
return m_nInstruments;
}
struct MThd
{
uint32be headerLength;
uint16be format; // 0 = single-track, 1 = multi-track, 2 = multi-song
uint16be numTracks; // Number of track chunks
uint16be division; // Delta timing value: positive = units/beat; negative = smpte compatible units
};
MPT_BINARY_STRUCT(MThd, 10)
using tick_t = uint32;
struct TrackState
{
FileReader track;
tick_t nextEvent = 0;
uint8 command = 0;
bool finished = false;
};
struct ModChannelState
{
static constexpr uint8 NOMIDI = 0xFF; // No MIDI channel assigned.
tick_t age = 0; // At which MIDI tick the channel was triggered
int32 porta = 0; // Current portamento position in extra-fine slide units (1/64th of a semitone)
uint8 vol = 100; // MIDI note volume (0...127)
uint8 pan = 128; // MIDI channel panning (0...256)
uint8 midiCh = NOMIDI; // MIDI channel that was last played on this channel
ModCommand::NOTE note = NOTE_NONE; // MIDI note that was last played on this channel
bool sustained = false; // If true, the note was already released by a note-off event, but sustain pedal CC is still active
};
struct MidiChannelState
{
int32 pitchbendMod = 0; // Pre-computed pitchbend in extra-fine slide units (1/64th of a semitone)
int16 pitchbend = MIDIEvents::pitchBendCentre; // 0...16383
uint16 bank = 0; // 0...16383
uint8 program = 0; // 0...127
// -- Controllers ---------------- function ---------- CC# --- range ---- init (midi) ---
uint8 pan = 128; // Channel Panning 10 [0-255] 128 (64)
uint8 expression = 128; // Channel Expression 11 0-128 128 (127)
uint8 volume = 80; // Channel Volume 7 0-128 80 (100)
uint16 rpn = 0x3FFF; // Currently selected RPN 100/101 n/a
uint8 pitchBendRange = 2; // Pitch Bend Range 2
int8 transpose = 0; // Channel transpose 0
bool monoMode = false; // Mono/Poly operation 126/127 n/a Poly
bool sustain = false; // Sustain pedal 64 on/off off
std::array<CHANNELINDEX, 128> noteOn; // Value != CHANNELINDEX_INVALID: Note is active and mapped to mod channel in value
MidiChannelState()
{
noteOn.fill(CHANNELINDEX_INVALID);
}
void SetPitchbend(uint16 value)
{
pitchbend = value;
// Convert from arbitrary MIDI pitchbend to 64th of semitone
pitchbendMod = Util::muldiv(pitchbend - MIDIEvents::pitchBendCentre, pitchBendRange * 64, MIDIEvents::pitchBendCentre);
}
void ResetAllControllers()
{
expression = 128;
pitchBendRange = 2;
SetPitchbend(MIDIEvents::pitchBendCentre);
transpose = 0;
rpn = 0x3FFF;
monoMode = false;
sustain = false;
// Should also reset modulation, pedals (40h-43h), aftertouch
}
void SetRPN(uint8 value)
{
switch(rpn)
{
case 0: // Pitch Bend Range
pitchBendRange = std::max(value, uint8(1));
SetPitchbend(pitchbend);
break;
case 2: // Coarse Tune
transpose = static_cast<int8>(value) - 64;
break;
}
}
void SetRPNRelative(int8 value)
{
switch(rpn)
{
case 0: // Pitch Bend Range
pitchBendRange = static_cast<uint8>(std::clamp(pitchBendRange + value, 1, 0x7F));
break;
case 2: // Coarse Tune
transpose = mpt::saturate_cast<int8>(transpose + value);
break;
}
}
};
static CHANNELINDEX FindUnusedChannel(uint8 midiCh, ModCommand::NOTE note, const std::vector<ModChannelState> &channels, bool monoMode, PatternRow patRow)
{
for(size_t i = 0; i < channels.size(); i++)
{
// Check if this note is already playing, or find any note of the same MIDI channel in case of mono mode
if(channels[i].midiCh == midiCh && (channels[i].note == note || (monoMode && channels[i].note != NOTE_NONE)))
{
return static_cast<CHANNELINDEX>(i);
}
}
CHANNELINDEX anyUnusedChannel = CHANNELINDEX_INVALID;
CHANNELINDEX anyFreeChannel = CHANNELINDEX_INVALID;
CHANNELINDEX oldsetMidiCh = CHANNELINDEX_INVALID;
tick_t oldestMidiChAge = std::numeric_limits<decltype(oldestMidiChAge)>::max();
CHANNELINDEX oldestAnyCh = 0;
tick_t oldestAnyChAge = std::numeric_limits<decltype(oldestAnyChAge)>::max();
for(size_t i = 0; i < channels.size(); i++)
{
if(channels[i].note == NOTE_NONE && !patRow[i].IsNote())
{
// Recycle channel previously used by the same MIDI channel
if(channels[i].midiCh == midiCh)
return static_cast<CHANNELINDEX>(i);
// If we cannot find a channel that was already used for the same MIDI channel, try a completely unused channel next
else if(channels[i].midiCh == ModChannelState::NOMIDI && anyUnusedChannel == CHANNELINDEX_INVALID)
anyUnusedChannel = static_cast<CHANNELINDEX>(i);
// And if that fails, try any channel that currently doesn't play a note.
if(anyFreeChannel == CHANNELINDEX_INVALID)
anyFreeChannel = static_cast<CHANNELINDEX>(i);
}
// If we can't find any free channels, look for the oldest channels
if(channels[i].midiCh == midiCh && channels[i].age < oldestMidiChAge)
{
// Oldest channel matching this MIDI channel
oldestMidiChAge = channels[i].age;
oldsetMidiCh = static_cast<CHANNELINDEX>(i);
} else if(channels[i].age < oldestAnyChAge)
{
// Any oldest channel
oldestAnyChAge = channels[i].age;
oldestAnyCh = static_cast<CHANNELINDEX>(i);
}
}
if(anyUnusedChannel != CHANNELINDEX_INVALID)
return anyUnusedChannel;
if(anyFreeChannel != CHANNELINDEX_INVALID)
return anyFreeChannel;
if(oldsetMidiCh != CHANNELINDEX_INVALID)
return oldsetMidiCh;
return oldestAnyCh;
}
static void MIDINoteOff(MidiChannelState &midiChn, std::vector<ModChannelState> &modChnStatus, uint8 note, uint8 delay, PatternRow patRow, std::bitset<16> drumChns)
{
CHANNELINDEX chn = midiChn.noteOn[note];
if(chn == CHANNELINDEX_INVALID)
return;
if(midiChn.sustain)
{
// Turn this off later
modChnStatus[chn].sustained = true;
return;
}
uint8 midiCh = modChnStatus[chn].midiCh;
modChnStatus[chn].note = NOTE_NONE;
modChnStatus[chn].sustained = false;
midiChn.noteOn[note] = CHANNELINDEX_INVALID;
ModCommand &m = patRow[chn];
if(m.note == NOTE_NONE)
{
m.note = NOTE_KEYOFF;
if(delay != 0)
{
m.command = CMD_S3MCMDEX;
m.param = 0xD0 | delay;
}
} else if(m.IsNote() && !drumChns[midiCh])
{
// Only do note cuts for melodic instruments - they sound weird on drums which should fade out naturally.
if(m.command == CMD_S3MCMDEX && (m.param & 0xF0) == 0xD0)
{
// Already have a note delay
m.command = CMD_DELAYCUT;
m.param = (m.param << 4) | (delay - (m.param & 0x0F));
} else if(m.command == CMD_NONE || m.command == CMD_PANNING8)
{
m.command = CMD_S3MCMDEX;
m.param = 0xC0 | delay;
}
}
}
static void EnterMIDIVolume(ModCommand &m, ModChannelState &modChn, const MidiChannelState &midiChn)
{
m.volcmd = VOLCMD_VOLUME;
int32 vol = CDLSBank::DLSMidiVolumeToLinear(modChn.vol) >> 8;
vol = (vol * midiChn.volume * midiChn.expression) >> 13;
Limit(vol, 4, 256);
m.vol = static_cast<ModCommand::VOL>(vol / 4);
}
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderMID(MemoryFileReader file, const uint64 *pfilesize)
{
MPT_UNREFERENCED_PARAMETER(pfilesize);
char magic[4];
file.ReadArray(magic);
if(!memcmp(magic, "MThd", 4))
return ProbeSuccess;
if(!memcmp(magic, "RIFF", 4) && file.Skip(4) && file.ReadMagic("RMID"))
return ProbeSuccess;
return ProbeFailure;
}
bool CSoundFile::ReadMID(FileReader &file, ModLoadingFlags loadFlags)
{
file.Rewind();
// Microsoft MIDI files
bool isRIFF = false;
if(file.ReadMagic("RIFF"))
{
file.Skip(4);
if(!file.ReadMagic("RMID"))
{
return false;
} else if(loadFlags == onlyVerifyHeader)
{
return true;
}
do
{
char id[4];
file.ReadArray(id);
uint32 length = file.ReadUint32LE();
if(memcmp(id, "data", 4))
{
file.Skip(length);
} else
{
isRIFF = true;
break;
}
} while(file.CanRead(8));
}
MThd fileHeader;
if(!file.ReadMagic("MThd")
|| !file.ReadStruct(fileHeader)
|| fileHeader.numTracks == 0
|| fileHeader.headerLength < 6
|| !file.Skip(fileHeader.headerLength - 6))
{
return false;
} else if(loadFlags == onlyVerifyHeader)
{
return true;
}
InitializeGlobals(MOD_TYPE_MID);
InitializeChannels();
#ifdef MODPLUG_TRACKER
const uint32 quantize = Clamp(TrackerSettings::Instance().midiImportQuantize.Get(), 4u, 256u);
const ROWINDEX patternLen = Clamp(TrackerSettings::Instance().midiImportPatternLen.Get(), ROWINDEX(1), MAX_PATTERN_ROWS);
const uint8 ticksPerRow = Clamp(TrackerSettings::Instance().midiImportTicks.Get(), uint8(2), uint8(16));
#else
const uint32 quantize = 32; // Must be 4 or higher
const ROWINDEX patternLen = 128;
const uint8 ticksPerRow = 16; // Must be in range 2...16
#endif
#ifdef MPT_FUZZ_TRACKER
// Avoid generating test cases that take overly long to evaluate
const ORDERINDEX MPT_MIDI_IMPORT_MAX_ORDERS = 64;
#else
const ORDERINDEX MPT_MIDI_IMPORT_MAX_ORDERS = MAX_ORDERS;
#endif
m_songArtist = U_("MIDI Conversion");
m_modFormat.formatName = U_("Standard MIDI File");
m_modFormat.type = isRIFF ? UL_("rmi") : UL_("mid");
m_modFormat.madeWithTracker = U_("Standard MIDI File");
m_modFormat.charset = mpt::Charset::ISO8859_1;
SetMixLevels(MixLevels::v1_17RC3);
m_nTempoMode = TempoMode::Modern;
m_SongFlags = SONG_LINEARSLIDES;
m_nDefaultTempo.Set(120);
m_nDefaultSpeed = ticksPerRow;
m_nChannels = MAX_BASECHANNELS;
m_nDefaultRowsPerBeat = quantize / 4;
m_nDefaultRowsPerMeasure = 4 * m_nDefaultRowsPerBeat;
m_nSamplePreAmp = m_nVSTiVolume = 32;
TEMPO tempo = m_nDefaultTempo;
uint16 ppqn = fileHeader.division;
if(ppqn & 0x8000)
{
// SMPTE compatible units (approximation)
int frames = 256 - (ppqn >> 8), subFrames = (ppqn & 0xFF);
ppqn = static_cast<uint16>(frames * subFrames / 2);
}
if(!ppqn)
ppqn = 96;
Order().clear();
MidiChannelState midiChnStatus[16];
const CHANNELINDEX tempoChannel = m_nChannels - 2, globalVolChannel = m_nChannels - 1;
const uint16 numTracks = fileHeader.numTracks;
std::vector<TrackState> tracks(numTracks);
std::vector<ModChannelState> modChnStatus(m_nChannels);
std::bitset<16> drumChns;
drumChns.set(MIDI_DRUMCHANNEL - 1);
tick_t timeShift = 0;
for(auto &track : tracks)
{
if(!file.ReadMagic("MTrk"))
return false;
track.track = file.ReadChunk(file.ReadUint32BE());
tick_t delta = 0;
track.track.ReadVarInt(delta);
// Work-around for some MID files that assume that negative deltas exist (they don't according to the standard)
if(delta > int32_max)
timeShift = std::max(static_cast<tick_t>(~delta + 1), timeShift);
track.nextEvent = delta;
}
if(timeShift != 0)
{
for(auto &track : tracks)
{
if(track.nextEvent > int32_max)
track.nextEvent = timeShift - static_cast<tick_t>(~track.nextEvent + 1);
else
track.nextEvent += timeShift;
}
}
uint16 finishedTracks = 0;
PATTERNINDEX emptyPattern = PATTERNINDEX_INVALID;
ORDERINDEX lastOrd = 0, loopEndOrd = ORDERINDEX_INVALID;
ROWINDEX lastRow = 0, loopEndRow = ROWINDEX_INVALID;
ROWINDEX restartRow = ROWINDEX_INVALID;
int8 masterTranspose = 0;
bool isXG = false;
bool isEMIDI = false;
bool isEMIDILoop = false;
const bool isType2 = (fileHeader.format == 2);
const auto ModPositionFromTick = [&](const tick_t tick, const tick_t offset = 0)
{
tick_t modTicks = Util::muldivr_unsigned(tick, quantize * ticksPerRow, ppqn * 4u) - offset;
ORDERINDEX ord = static_cast<ORDERINDEX>((modTicks / ticksPerRow) / patternLen);
ROWINDEX row = (modTicks / ticksPerRow) % patternLen;
uint8 delay = static_cast<uint8>(modTicks % ticksPerRow);
return std::make_tuple(ord, row, delay);
};
while(finishedTracks < numTracks)
{
uint16 t = 0;
tick_t tick = std::numeric_limits<decltype(tick)>::max();
for(uint16 track = 0; track < numTracks; track++)
{
if(!tracks[track].finished && tracks[track].nextEvent < tick)
{
tick = tracks[track].nextEvent;
t = track;
if(isType2)
break;
}
}
FileReader &track = tracks[t].track;
const auto [ord, row, delay] = ModPositionFromTick(tick);
if(ord >= Order().GetLength())
{
if(ord > MPT_MIDI_IMPORT_MAX_ORDERS)
break;
ORDERINDEX curSize = Order().GetLength();
// If we need to extend the order list by more than one pattern, this means that we
// will be filling in empty patterns. Just recycle one empty pattern for this job.
// We read events in chronological order, so it is never possible for the loader to
// "jump back" to one of those empty patterns and write into it.
if(ord > curSize && emptyPattern == PATTERNINDEX_INVALID)
{
if((emptyPattern = Patterns.InsertAny(patternLen)) == PATTERNINDEX_INVALID)
break;
}
Order().resize(ord + 1, emptyPattern);
if((Order()[ord] = Patterns.InsertAny(patternLen)) == PATTERNINDEX_INVALID)
break;
}
// Keep track of position of last event for resizing the last pattern
if(ord > lastOrd)
{
lastOrd = ord;
lastRow = row;
} else if(ord == lastOrd)
{
lastRow = std::max(lastRow, row);
}
PATTERNINDEX pat = Order()[ord];
PatternRow patRow = Patterns[pat].GetRow(row);
uint8 data1 = track.ReadUint8();
if(data1 == 0xFF)
{
// Meta events
data1 = track.ReadUint8();
size_t len = 0;
track.ReadVarInt(len);
FileReader chunk = track.ReadChunk(len);
switch(data1)
{
case 1: // Text
case 2: // Copyright
m_songMessage.Read(chunk, len, SongMessage::leAutodetect);
break;
case 3: // Track Name
if(len > 0)
{
std::string s;
chunk.ReadString<mpt::String::maybeNullTerminated>(s, len);
if(!m_songMessage.empty())
m_songMessage.append(1, SongMessage::InternalLineEnding);
m_songMessage += s;
if(m_songName.empty())
m_songName = s;
}
break;
case 4: // Instrument
case 5: // Lyric
break;
case 6: // Marker
case 7: // Cue point
{
std::string s;
chunk.ReadString<mpt::String::maybeNullTerminated>(s, len);
Patterns[pat].SetName(s);
if(!mpt::CompareNoCaseAscii(s, "loopStart"))
{
Order().SetRestartPos(ord);
restartRow = row;
} else if(!mpt::CompareNoCaseAscii(s, "loopEnd"))
{
std::tie(loopEndOrd, loopEndRow, std::ignore) = ModPositionFromTick(tick, 1);
}
}
break;
case 8: // Patch name
case 9: // Port name
break;
case 0x2F: // End Of Track
tracks[t].finished = true;
break;
case 0x51: // Tempo
{
uint32 tempoInt = chunk.ReadUint24BE();
if(tempoInt == 0)
break;
TEMPO newTempo(60000000.0 / tempoInt);
if(!tick)
{
m_nDefaultTempo = newTempo;
} else if(newTempo != tempo)
{
patRow[tempoChannel].command = CMD_TEMPO;
patRow[tempoChannel].param = mpt::saturate_round<ModCommand::PARAM>(std::max(32.0, newTempo.ToDouble()));
}
tempo = newTempo;
}
break;
default:
break;
}
} else
{
uint8 command = tracks[t].command;
if(data1 & 0x80)
{
// Command byte (if not present, use running status for channel messages)
command = data1;
if(data1 < 0xF0)
{
tracks[t].command = data1;
data1 = track.ReadUint8();
}
}
uint8 midiCh = command & 0x0F;
switch(command & 0xF0)
{
case 0x80: // Note Off
case 0x90: // Note On
{
data1 &= 0x7F;
ModCommand::NOTE note = static_cast<ModCommand::NOTE>(Clamp(data1 + NOTE_MIN, NOTE_MIN, NOTE_MAX));
uint8 data2 = track.ReadUint8();
if(data2 > 0 && (command & 0xF0) == 0x90)
{
// Note On
CHANNELINDEX chn = FindUnusedChannel(midiCh, note, modChnStatus, midiChnStatus[midiCh].monoMode, patRow);
if(chn != CHANNELINDEX_INVALID)
{
modChnStatus[chn].age = tick;
modChnStatus[chn].note = note;
modChnStatus[chn].midiCh = midiCh;
modChnStatus[chn].vol = data2;
modChnStatus[chn].sustained = false;
midiChnStatus[midiCh].noteOn[data1] = chn;
int32 pitchOffset = 0;
if(midiChnStatus[midiCh].pitchbendMod != 0)
{
pitchOffset = (midiChnStatus[midiCh].pitchbendMod + (midiChnStatus[midiCh].pitchbendMod > 0 ? 32 : -32)) / 64;
modChnStatus[chn].porta = pitchOffset * 64;
} else
{
modChnStatus[chn].porta = 0;
}
patRow[chn].note = static_cast<ModCommand::NOTE>(Clamp(note + pitchOffset + midiChnStatus[midiCh].transpose + masterTranspose, NOTE_MIN, NOTE_MAX));
patRow[chn].instr = mpt::saturate_cast<ModCommand::INSTR>(MapMidiInstrument(midiChnStatus[midiCh].program, midiChnStatus[midiCh].bank, midiCh + 1, data1, isXG, drumChns));
EnterMIDIVolume(patRow[chn], modChnStatus[chn], midiChnStatus[midiCh]);
if(patRow[chn].command == CMD_PORTAMENTODOWN || patRow[chn].command == CMD_PORTAMENTOUP)
{
patRow[chn].command = CMD_NONE;
}
if(delay != 0)
{
patRow[chn].command = CMD_S3MCMDEX;
patRow[chn].param = 0xD0 | delay;
}
if(modChnStatus[chn].pan != midiChnStatus[midiCh].pan && patRow[chn].command == CMD_NONE)
{
patRow[chn].command = CMD_PANNING8;
patRow[chn].param = midiChnStatus[midiCh].pan;
modChnStatus[chn].pan = midiChnStatus[midiCh].pan;
}
}
} else
{
// Note Off
MIDINoteOff(midiChnStatus[midiCh], modChnStatus, data1, delay, patRow, drumChns);
}
}
break;
case 0xA0: // Note Aftertouch
{
track.Skip(1);
}
break;
case 0xB0: // Controller
{
uint8 data2 = track.ReadUint8();
switch(data1)
{
case MIDIEvents::MIDICC_Panposition_Coarse:
midiChnStatus[midiCh].pan = data2 * 2u;
for(auto chn : midiChnStatus[midiCh].noteOn)
{
if(chn != CHANNELINDEX_INVALID && modChnStatus[chn].pan != midiChnStatus[midiCh].pan)
{
if(Patterns[pat].WriteEffect(EffectWriter(CMD_PANNING8, midiChnStatus[midiCh].pan).Channel(chn).Row(row)))
{
modChnStatus[chn].pan = midiChnStatus[midiCh].pan;
}
}
}
break;
case MIDIEvents::MIDICC_DataEntry_Coarse:
midiChnStatus[midiCh].SetRPN(data2);
break;
case MIDIEvents::MIDICC_Volume_Coarse:
midiChnStatus[midiCh].volume = (uint8)(CDLSBank::DLSMidiVolumeToLinear(data2) >> 9);
for(auto chn : midiChnStatus[midiCh].noteOn)
{
if(chn != CHANNELINDEX_INVALID)
{
EnterMIDIVolume(patRow[chn], modChnStatus[chn], midiChnStatus[midiCh]);
}
}
break;
case MIDIEvents::MIDICC_Expression_Coarse:
midiChnStatus[midiCh].expression = (uint8)(CDLSBank::DLSMidiVolumeToLinear(data2) >> 9);
for(auto chn : midiChnStatus[midiCh].noteOn)
{
if(chn != CHANNELINDEX_INVALID)
{
EnterMIDIVolume(patRow[chn], modChnStatus[chn], midiChnStatus[midiCh]);
}
}
break;
case MIDIEvents::MIDICC_BankSelect_Coarse:
midiChnStatus[midiCh].bank &= 0x7F;
midiChnStatus[midiCh].bank |= (data2 << 7);
break;
case MIDIEvents::MIDICC_BankSelect_Fine:
midiChnStatus[midiCh].bank &= (0x7F << 7);
midiChnStatus[midiCh].bank |= data2;
break;
case MIDIEvents::MIDICC_HoldPedal_OnOff:
midiChnStatus[midiCh].sustain = (data2 >= 0x40);
if(data2 < 0x40)
{
// Release notes that are still being held after note-off
for(const auto &chnState : modChnStatus)
{
if(chnState.midiCh == midiCh && chnState.sustained && chnState.note != NOTE_NONE)
{
MIDINoteOff(midiChnStatus[midiCh], modChnStatus, chnState.note - NOTE_MIN, delay, patRow, drumChns);
}
}
}
break;
case MIDIEvents::MIDICC_DataButtonincrement:
case MIDIEvents::MIDICC_DataButtondecrement:
midiChnStatus[midiCh].SetRPNRelative((data1 == MIDIEvents::MIDICC_DataButtonincrement) ? 1 : -1);
break;
case MIDIEvents::MIDICC_NonRegisteredParameter_Fine:
case MIDIEvents::MIDICC_NonRegisteredParameter_Coarse:
midiChnStatus[midiCh].rpn = 0x3FFF;
break;
case MIDIEvents::MIDICC_RegisteredParameter_Fine:
midiChnStatus[midiCh].rpn &= (0x7F << 7);
midiChnStatus[midiCh].rpn |= data2;
break;
case MIDIEvents::MIDICC_RegisteredParameter_Coarse:
midiChnStatus[midiCh].rpn &= 0x7F;
midiChnStatus[midiCh].rpn |= (data2 << 7);
break;
case 110:
isEMIDI = true;
break;
case 111:
// Non-standard MIDI loop point. May conflict with Apogee EMIDI CCs (110/111), which is why we also check if CC 110 is ever used.
if(data2 == 0 && !isEMIDI)
{
Order().SetRestartPos(ord);
restartRow = row;
}
break;
case 118:
// EMIDI Global Loop Start
isEMIDI = true;
isEMIDILoop = false;
Order().SetRestartPos(ord);
restartRow = row;
break;
case 119:
// EMIDI Global Loop End
if(data2 == 0x7F)
{
isEMIDILoop = true;
isEMIDI = true;
std::tie(loopEndOrd, loopEndRow, std::ignore) = ModPositionFromTick(tick, 1);
}
break;
case MIDIEvents::MIDICC_AllControllersOff:
midiChnStatus[midiCh].ResetAllControllers();
break;
// Bn.78.00: All Sound Off (GS)
// Bn.7B.00: All Notes Off (GM)
case MIDIEvents::MIDICC_AllSoundOff:
case MIDIEvents::MIDICC_AllNotesOff:
// All Notes Off
midiChnStatus[midiCh].sustain = false;
for(uint8 note = 0; note < 128; note++)
{
MIDINoteOff(midiChnStatus[midiCh], modChnStatus, note, delay, patRow, drumChns);
}
break;
case MIDIEvents::MIDICC_MonoOperation:
if(data2 == 0)
{
midiChnStatus[midiCh].monoMode = true;
}
break;
case MIDIEvents::MIDICC_PolyOperation:
if(data2 == 0)
{
midiChnStatus[midiCh].monoMode = false;
}
break;
}
}
break;
case 0xC0: // Program Change
midiChnStatus[midiCh].program = data1 & 0x7F;
break;
case 0xD0: // Channel aftertouch
break;
case 0xE0: // Pitch bend
midiChnStatus[midiCh].SetPitchbend(data1 | (track.ReadUint8() << 7));
break;
case 0xF0: // General / Immediate
switch(midiCh)
{
case MIDIEvents::sysExStart: // SysEx
case MIDIEvents::sysExEnd: // SysEx (continued)
{
uint32 len;
track.ReadVarInt(len);
FileReader sysex = track.ReadChunk(len);
if(midiCh == MIDIEvents::sysExEnd)
break;
if(sysex.ReadMagic("\x7F\x7F\x04\x01"))
{
// Master volume
uint8 volumeRaw[2];
sysex.ReadArray(volumeRaw);
uint16 globalVol = volumeRaw[0] | (volumeRaw[1] << 7);
if(tick == 0)
{
m_nDefaultGlobalVolume = Util::muldivr_unsigned(globalVol, MAX_GLOBAL_VOLUME, 16383);
} else
{
patRow[globalVolChannel].command = CMD_GLOBALVOLUME;
patRow[globalVolChannel].param = static_cast<ModCommand::PARAM>(Util::muldivr_unsigned(globalVol, 128, 16383));
}
} else
{
uint8 xg[7];
sysex.ReadArray(xg);
if(!memcmp(xg, "\x43\x10\x4C\x00\x00\x7E\x00", 7))
{
// XG System On
isXG = true;
} else if(!memcmp(xg, "\x43\x10\x4C\x00\x00\x06", 6))
{
// XG Master Transpose
masterTranspose = static_cast<int8>(xg[6]) - 64;
} else if(!memcmp(xg, "\x41\x10\x42\x12\x40", 5) && (xg[5] & 0xF0) == 0x10 && xg[6] == 0x15)
{
// GS Drum Kit
uint8 chn = xg[5] & 0x0F;
if(chn == 0)
chn = 9;
else if(chn < 10)
chn--;
drumChns.set(chn, sysex.ReadUint8() != 0);
}
}
}
break;
case MIDIEvents::sysQuarterFrame:
track.Skip(1);
break;
case MIDIEvents::sysPositionPointer:
track.Skip(2);
break;
case MIDIEvents::sysSongSelect:
track.Skip(1);
break;
case MIDIEvents::sysTuneRequest:
case MIDIEvents::sysMIDIClock:
case MIDIEvents::sysMIDITick:
case MIDIEvents::sysStart:
case MIDIEvents::sysContinue:
case MIDIEvents::sysStop:
case MIDIEvents::sysActiveSense:
case MIDIEvents::sysReset:
break;
default:
break;
}
break;
default:
break;
}
}
// Pitch bend any channels that haven't reached their target yet
// TODO: This is currently not called on any rows without events!
for(size_t chn = 0; chn < modChnStatus.size(); chn++)
{
ModChannelState &chnState = modChnStatus[chn];
ModCommand &m = patRow[chn];
uint8 midiCh = chnState.midiCh;
if(chnState.note == NOTE_NONE || m.command == CMD_S3MCMDEX || m.command == CMD_DELAYCUT || midiCh == ModChannelState::NOMIDI)
continue;
int32 diff = midiChnStatus[midiCh].pitchbendMod - chnState.porta;
if(diff == 0)
continue;
if(m.command == CMD_PORTAMENTODOWN || m.command == CMD_PORTAMENTOUP)
{
// First, undo the effect of an existing portamento command
int32 porta = 0;
if(m.param < 0xE0)
porta = m.param * 4 * (ticksPerRow - 1);
else if(m.param < 0xF0)
porta = (m.param & 0x0F);
else
porta = (m.param & 0x0F) * 4;
if(m.command == CMD_PORTAMENTODOWN)
porta = -porta;
diff += porta;
chnState.porta -= porta;
if(diff == 0)
{
m.command = CMD_NONE;
continue;
}
}
m.command = static_cast<ModCommand::COMMAND>(diff < 0 ? CMD_PORTAMENTODOWN : CMD_PORTAMENTOUP);
int32 absDiff = std::abs(diff);
int32 realDiff = 0;
if(absDiff < 16)
{
// Extra-fine slides can do this.
m.param = 0xE0 | static_cast<uint8>(absDiff);
realDiff = absDiff;
} else if(absDiff < 64)
{
// Fine slides can do this.
absDiff = std::min((absDiff + 3) / 4, 0x0F);
m.param = 0xF0 | static_cast<uint8>(absDiff);
realDiff = absDiff * 4;
} else
{
// Need a normal slide.
absDiff /= 4 * (ticksPerRow - 1);
LimitMax(absDiff, 0xDF);
m.param = static_cast<uint8>(absDiff);
realDiff = absDiff * 4 * (ticksPerRow - 1);
}
chnState.porta += realDiff * mpt::signum(diff);
}
tick_t delta = 0;
if(track.ReadVarInt(delta) && track.CanRead(1))
{
tracks[t].nextEvent += delta;
} else
{
finishedTracks++;
tracks[t].nextEvent = Util::MaxValueOfType(delta);
tracks[t].finished = true;
// Add another sub-song for type-2 files
if(isType2 && finishedTracks < numTracks)
{
if(Order.AddSequence() == SEQUENCEINDEX_INVALID)
break;
Order().clear();
}
}
}
if(isEMIDILoop)
isEMIDI = false;
if(isEMIDI)
{
Order().SetRestartPos(0);
}
if(loopEndOrd == ORDERINDEX_INVALID)
loopEndOrd = lastOrd;
if(loopEndRow == ROWINDEX_INVALID)
loopEndRow = lastRow;
if(Order().IsValidPat(loopEndOrd))
{
PATTERNINDEX lastPat = Order()[loopEndOrd];
if(loopEndOrd == lastOrd)
Patterns[lastPat].Resize(loopEndRow + 1);
if(restartRow != ROWINDEX_INVALID && !isEMIDI)
{
Patterns[lastPat].WriteEffect(EffectWriter(CMD_PATTERNBREAK, mpt::saturate_cast<ModCommand::PARAM>(restartRow)).Row(loopEndRow));
if(ORDERINDEX restartPos = Order().GetRestartPos(); loopEndOrd != lastOrd || restartPos <= std::numeric_limits<ModCommand::PARAM>::max())
Patterns[lastPat].WriteEffect(EffectWriter(CMD_POSITIONJUMP, mpt::saturate_cast<ModCommand::PARAM>(restartPos)).Row(loopEndRow));
}
}
Order.SetSequence(0);
std::vector<CHANNELINDEX> channels;
channels.reserve(m_nChannels);
for(CHANNELINDEX i = 0; i < m_nChannels; i++)
{
if(modChnStatus[i].midiCh != ModChannelState::NOMIDI
#ifdef MODPLUG_TRACKER
|| (GetpModDoc() != nullptr && !GetpModDoc()->IsChannelUnused(i))
#endif // MODPLUG_TRACKER
)
{
channels.push_back(i);
if(modChnStatus[i].midiCh != ModChannelState::NOMIDI)
ChnSettings[i].szName = MPT_AFORMAT("MIDI Ch {}")(1 + modChnStatus[i].midiCh);
else if(i == tempoChannel)
ChnSettings[i].szName = "Tempo";
else if(i == globalVolChannel)
ChnSettings[i].szName = "Global Volume";
}
}
if(channels.empty())
return false;
#ifdef MODPLUG_TRACKER
if(GetpModDoc() != nullptr)
{
// Keep MIDI channels in patterns neatly grouped
std::sort(channels.begin(), channels.end(), [&modChnStatus] (CHANNELINDEX c1, CHANNELINDEX c2)
{
if(modChnStatus[c1].midiCh == modChnStatus[c2].midiCh)
return c1 < c2;
return modChnStatus[c1].midiCh < modChnStatus[c2].midiCh;
});
GetpModDoc()->ReArrangeChannels(channels, false);
GetpModDoc()->m_ShowSavedialog = true;
}
std::unique_ptr<CDLSBank> cachedBank, embeddedBank;
if(CDLSBank::IsDLSBank(file.GetOptionalFileName().value_or(P_(""))))
{
// Soundfont embedded in MIDI file
embeddedBank = std::make_unique<CDLSBank>();
embeddedBank->Open(file.GetOptionalFileName().value_or(P_("")));
} else
{
// Soundfont with same name as MIDI file
for(const auto &ext : { P_(".sf2"), P_(".sf3"), P_(".sf4"), P_(".sbk"), P_(".dls") })
{
mpt::PathString filename = file.GetOptionalFileName().value_or(P_("")).ReplaceExt(ext);
if(filename.IsFile())
{
embeddedBank = std::make_unique<CDLSBank>();
if(embeddedBank->Open(filename))
break;
}
}
}
ChangeModTypeTo(MOD_TYPE_MPT);
const MidiLibrary &midiLib = CTrackApp::GetMidiLibrary();
mpt::PathString cachedBankName;
// Load Instruments
for (INSTRUMENTINDEX ins = 1; ins <= m_nInstruments; ins++) if (Instruments[ins])
{
ModInstrument *pIns = Instruments[ins];
uint32 midiCode = 0;
if(pIns->nMidiChannel == MIDI_DRUMCHANNEL)
midiCode = 0x80 | (pIns->nMidiDrumKey & 0x7F);
else if(pIns->nMidiProgram)
midiCode = (pIns->nMidiProgram - 1) & 0x7F;
if(embeddedBank && embeddedBank->FindAndExtract(*this, ins, midiCode >= 0x80))
{
continue;
}
const mpt::PathString &midiMapName = midiLib[midiCode];
if(!midiMapName.empty())
{
// Load from DLS/SF2 Bank
if(CDLSBank::IsDLSBank(midiMapName))
{
CDLSBank *dlsBank = nullptr;
if(cachedBank != nullptr && !mpt::PathString::CompareNoCase(cachedBankName, midiMapName))
{
dlsBank = cachedBank.get();
} else
{
cachedBank = std::make_unique<CDLSBank>();
cachedBankName = midiMapName;
if(cachedBank->Open(midiMapName)) dlsBank = cachedBank.get();
}
if(dlsBank)
{
dlsBank->FindAndExtract(*this, ins, midiCode >= 0x80);
}
} else
{
// Load from Instrument or Sample file
InputFile f(midiMapName, SettingCacheCompleteFileBeforeLoading());
if(f.IsValid())
{
FileReader insFile = GetFileReader(f);
if(ReadInstrumentFromFile(ins, insFile, false))
{
mpt::PathString filename = midiMapName.GetFullFileName();
pIns = Instruments[ins];
if(!pIns->filename[0]) pIns->filename = filename.ToLocale();
if(!pIns->name[0])
{
if(midiCode < 0x80)
{
pIns->name = szMidiProgramNames[midiCode];
} else
{
uint32 key = midiCode & 0x7F;
if((key >= 24) && (key < 24 + std::size(szMidiPercussionNames)))
pIns->name = szMidiPercussionNames[key - 24];
}
}
}
}
}
}
}
#endif // MODPLUG_TRACKER
return true;
}
#else // !MODPLUG_TRACKER && !MPT_FUZZ_TRACKER
bool CSoundFile::ReadMID(FileReader &/*file*/, ModLoadingFlags /*loadFlags*/)
{
return false;
}
#endif
OPENMPT_NAMESPACE_END