1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405 |
- /*
- * 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
|