123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667 |
- /*
- * Load_imf.cpp
- * ------------
- * Purpose: IMF (Imago Orpheus) module loader
- * Notes : Reverb and Chorus are not supported.
- * Authors: Storlek (Original author - http://schismtracker.org/ - code ported with permission)
- * Johannes Schultz (OpenMPT Port, tweaks)
- * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
- */
- #include "stdafx.h"
- #include "Loaders.h"
- OPENMPT_NAMESPACE_BEGIN
- struct IMFChannel
- {
- char name[12]; // Channel name (ASCIIZ-String, max 11 chars)
- uint8 chorus; // Default chorus
- uint8 reverb; // Default reverb
- uint8 panning; // Pan positions 00-FF
- uint8 status; // Channel status: 0 = enabled, 1 = mute, 2 = disabled (ignore effects!)
- };
- MPT_BINARY_STRUCT(IMFChannel, 16)
- struct IMFFileHeader
- {
- enum SongFlags
- {
- linearSlides = 0x01,
- };
- char title[32]; // Songname (ASCIIZ-String, max. 31 chars)
- uint16le ordNum; // Number of orders saved
- uint16le patNum; // Number of patterns saved
- uint16le insNum; // Number of instruments saved
- uint16le flags; // See SongFlags
- uint8le unused1[8];
- uint8le tempo; // Default tempo (Axx, 1...255)
- uint8le bpm; // Default beats per minute (BPM) (Txx, 32...255)
- uint8le master; // Default master volume (Vxx, 0...64)
- uint8le amp; // Amplification factor (mixing volume, 4...127)
- uint8le unused2[8];
- char im10[4]; // 'IM10'
- IMFChannel channels[32]; // Channel settings
- };
- MPT_BINARY_STRUCT(IMFFileHeader, 576)
- struct IMFEnvelope
- {
- enum EnvFlags
- {
- envEnabled = 0x01,
- envSustain = 0x02,
- envLoop = 0x04,
- };
- uint8 points; // Number of envelope points
- uint8 sustain; // Envelope sustain point
- uint8 loopStart; // Envelope loop start point
- uint8 loopEnd; // Envelope loop end point
- uint8 flags; // See EnvFlags
- uint8 unused[3];
- };
- MPT_BINARY_STRUCT(IMFEnvelope, 8)
- struct IMFEnvNode
- {
- uint16le tick;
- uint16le value;
- };
- MPT_BINARY_STRUCT(IMFEnvNode, 4)
- struct IMFInstrument
- {
- enum EnvTypes
- {
- volEnv = 0,
- panEnv = 1,
- filterEnv = 2,
- };
- char name[32]; // Inst. name (ASCIIZ-String, max. 31 chars)
- uint8le map[120]; // Multisample settings
- uint8le unused[8];
- IMFEnvNode nodes[3][16];
- IMFEnvelope env[3];
- uint16le fadeout; // Fadeout rate (0...0FFFH)
- uint16le smpNum; // Number of samples in instrument
- char ii10[4]; // 'II10' (not verified by Orpheus)
- void ConvertEnvelope(InstrumentEnvelope &mptEnv, EnvTypes e) const
- {
- const uint8 shift = (e == volEnv) ? 0 : 2;
- const uint8 mirror = (e == filterEnv) ? 0xFF : 0x00;
- mptEnv.dwFlags.set(ENV_ENABLED, (env[e].flags & 1) != 0);
- mptEnv.dwFlags.set(ENV_SUSTAIN, (env[e].flags & 2) != 0);
- mptEnv.dwFlags.set(ENV_LOOP, (env[e].flags & 4) != 0);
- mptEnv.resize(Clamp(env[e].points, uint8(2), uint8(16)));
- mptEnv.nLoopStart = env[e].loopStart;
- mptEnv.nLoopEnd = env[e].loopEnd;
- mptEnv.nSustainStart = mptEnv.nSustainEnd = env[e].sustain;
- uint16 minTick = 0; // minimum tick value for next node
- for(uint32 n = 0; n < mptEnv.size(); n++)
- {
- mptEnv[n].tick = minTick = std::max(minTick, nodes[e][n].tick.get());
- minTick++;
- uint8 value = static_cast<uint8>(nodes[e][n].value ^ mirror) >> shift;
- mptEnv[n].value = std::min(value, uint8(ENVELOPE_MAX));
- }
- mptEnv.Convert(MOD_TYPE_XM, MOD_TYPE_IT);
- }
- // Convert an IMFInstrument to OpenMPT's internal instrument representation.
- void ConvertToMPT(ModInstrument &mptIns, SAMPLEINDEX firstSample) const
- {
- mptIns.name = mpt::String::ReadBuf(mpt::String::nullTerminated, name);
- if(smpNum)
- {
- for(size_t note = 0; note < std::min(std::size(map), std::size(mptIns.Keyboard) - 12u); note++)
- {
- mptIns.Keyboard[note + 12] = firstSample + map[note];
- }
- }
- mptIns.nFadeOut = fadeout;
- mptIns.midiPWD = 1; // For CMD_FINETUNE
- ConvertEnvelope(mptIns.VolEnv, volEnv);
- ConvertEnvelope(mptIns.PanEnv, panEnv);
- ConvertEnvelope(mptIns.PitchEnv, filterEnv);
- if(mptIns.PitchEnv.dwFlags[ENV_ENABLED])
- mptIns.PitchEnv.dwFlags.set(ENV_FILTER);
- // hack to get === to stop notes
- if(!mptIns.VolEnv.dwFlags[ENV_ENABLED] && !mptIns.nFadeOut)
- mptIns.nFadeOut = 32767;
- }
- };
- MPT_BINARY_STRUCT(IMFInstrument, 384)
- struct IMFSample
- {
- enum SampleFlags
- {
- smpLoop = 0x01,
- smpPingPongLoop = 0x02,
- smp16Bit = 0x04,
- smpPanning = 0x08,
- };
- char filename[13]; // Sample filename (12345678.ABC) */
- uint8le unused1[3];
- uint32le length; // Length (in bytes)
- uint32le loopStart; // Loop start (in bytes)
- uint32le loopEnd; // Loop end (in bytes)
- uint32le c5Speed; // Samplerate
- uint8le volume; // Default volume (0...64)
- uint8le panning; // Default pan (0...255)
- uint8le unused2[14];
- uint8le flags; // Sample flags
- uint8le unused3[5];
- uint16le ems; // Reserved for internal usage
- uint32le dram; // Reserved for internal usage
- char is10[4]; // 'IS10'
- // Convert an IMFSample to OpenMPT's internal sample representation.
- void ConvertToMPT(ModSample &mptSmp) const
- {
- mptSmp.Initialize();
- mptSmp.filename = mpt::String::ReadBuf(mpt::String::nullTerminated, filename);
- mptSmp.nLength = length;
- mptSmp.nLoopStart = loopStart;
- mptSmp.nLoopEnd = loopEnd;
- mptSmp.nC5Speed = c5Speed;
- mptSmp.nVolume = volume * 4;
- mptSmp.nPan = panning;
- if(flags & smpLoop)
- mptSmp.uFlags.set(CHN_LOOP);
- if(flags & smpPingPongLoop)
- mptSmp.uFlags.set(CHN_PINGPONGLOOP);
- if(flags & smp16Bit)
- {
- mptSmp.uFlags.set(CHN_16BIT);
- mptSmp.nLength /= 2;
- mptSmp.nLoopStart /= 2;
- mptSmp.nLoopEnd /= 2;
- }
- if(flags & smpPanning)
- mptSmp.uFlags.set(CHN_PANNING);
- }
- };
- MPT_BINARY_STRUCT(IMFSample, 64)
- static constexpr EffectCommand imfEffects[] =
- {
- CMD_NONE,
- CMD_SPEED, // 0x01 1xx Set Tempo
- CMD_TEMPO, // 0x02 2xx Set BPM
- CMD_TONEPORTAMENTO, // 0x03 3xx Tone Portamento
- CMD_TONEPORTAVOL, // 0x04 4xy Tone Portamento + Volume Slide
- CMD_VIBRATO, // 0x05 5xy Vibrato
- CMD_VIBRATOVOL, // 0x06 6xy Vibrato + Volume Slide
- CMD_FINEVIBRATO, // 0x07 7xy Fine Vibrato
- CMD_TREMOLO, // 0x08 8xy Tremolo
- CMD_ARPEGGIO, // 0x09 9xy Arpeggio
- CMD_PANNING8, // 0x0A Axx Set Pan Position
- CMD_PANNINGSLIDE, // 0x0B Bxy Pan Slide
- CMD_VOLUME, // 0x0C Cxx Set Volume
- CMD_VOLUMESLIDE, // 0x0D Dxy Volume Slide
- CMD_VOLUMESLIDE, // 0x0E Exy Fine Volume Slide
- CMD_FINETUNE, // 0x0F Fxx Set Finetune
- CMD_NOTESLIDEUP, // 0x10 Gxy Note Slide Up
- CMD_NOTESLIDEDOWN, // 0x11 Hxy Note Slide Down
- CMD_PORTAMENTOUP, // 0x12 Ixx Slide Up
- CMD_PORTAMENTODOWN, // 0x13 Jxx Slide Down
- CMD_PORTAMENTOUP, // 0x14 Kxx Fine Slide Up
- CMD_PORTAMENTODOWN, // 0x15 Lxx Fine Slide Down
- CMD_MIDI, // 0x16 Mxx Set Filter Cutoff
- CMD_MIDI, // 0x17 Nxy Filter Slide + Resonance
- CMD_OFFSET, // 0x18 Oxx Set Sample Offset
- CMD_NONE, // 0x19 Pxx Set Fine Sample Offset - XXX
- CMD_KEYOFF, // 0x1A Qxx Key Off
- CMD_RETRIG, // 0x1B Rxy Retrig
- CMD_TREMOR, // 0x1C Sxy Tremor
- CMD_POSITIONJUMP, // 0x1D Txx Position Jump
- CMD_PATTERNBREAK, // 0x1E Uxx Pattern Break
- CMD_GLOBALVOLUME, // 0x1F Vxx Set Mastervolume
- CMD_GLOBALVOLSLIDE, // 0x20 Wxy Mastervolume Slide
- CMD_S3MCMDEX, // 0x21 Xxx Extended Effect
- // X1x Set Filter
- // X3x Glissando
- // X5x Vibrato Waveform
- // X8x Tremolo Waveform
- // XAx Pattern Loop
- // XBx Pattern Delay
- // XCx Note Cut
- // XDx Note Delay
- // XEx Ignore Envelope
- // XFx Invert Loop
- CMD_NONE, // 0x22 Yxx Chorus - XXX
- CMD_NONE, // 0x23 Zxx Reverb - XXX
- };
- static void ImportIMFEffect(ModCommand &m)
- {
- uint8 n;
- // fix some of them
- switch(m.command)
- {
- case 0xE: // fine volslide
- // hackaround to get almost-right behavior for fine slides (i think!)
- if(m.param == 0)
- /* nothing */;
- else if(m.param == 0xF0)
- m.param = 0xEF;
- else if(m.param == 0x0F)
- m.param = 0xFE;
- else if(m.param & 0xF0)
- m.param |= 0x0F;
- else
- m.param |= 0xF0;
- break;
- case 0xF: // set finetune
- m.param ^= 0x80;
- break;
- case 0x14: // fine slide up
- case 0x15: // fine slide down
- // this is about as close as we can do...
- if(m.param >> 4)
- m.param = 0xF0 | (m.param >> 4);
- else
- m.param |= 0xE0;
- break;
- case 0x16: // cutoff
- m.param = (0xFF - m.param) / 2u;
- break;
- case 0x17: // cutoff slide + resonance (TODO: cutoff slide is currently not handled)
- m.param = 0x80 | (m.param & 0x0F);
- break;
- case 0x1F: // set global volume
- m.param = mpt::saturate_cast<uint8>(m.param * 2);
- break;
- case 0x21:
- n = 0;
- switch (m.param >> 4)
- {
- case 0:
- /* undefined, but since S0x does nothing in IT anyway, we won't care.
- this is here to allow S00 to pick up the previous value (assuming IMF
- even does that -- I haven't actually tried it) */
- break;
- default: // undefined
- case 0x1: // set filter
- case 0xF: // invert loop
- m.command = CMD_NONE;
- break;
- case 0x3: // glissando
- n = 0x20;
- break;
- case 0x5: // vibrato waveform
- n = 0x30;
- break;
- case 0x8: // tremolo waveform
- n = 0x40;
- break;
- case 0xA: // pattern loop
- n = 0xB0;
- break;
- case 0xB: // pattern delay
- n = 0xE0;
- break;
- case 0xC: // note cut
- case 0xD: // note delay
- // Apparently, Imago Orpheus doesn't cut samples on tick 0.
- if(!m.param)
- m.command = CMD_NONE;
- break;
- case 0xE: // ignore envelope
- switch(m.param & 0x0F)
- {
- // All envelopes
- // Predicament: we can only disable one envelope at a time. Volume is probably most noticeable, so let's go with that.
- case 0: m.param = 0x77; break;
- // Volume
- case 1: m.param = 0x77; break;
- // Panning
- case 2: m.param = 0x79; break;
- // Filter
- case 3: m.param = 0x7B; break;
- }
- break;
- case 0x18: // sample offset
- // O00 doesn't pick up the previous value
- if(!m.param)
- m.command = CMD_NONE;
- break;
- }
- if(n)
- m.param = n | (m.param & 0x0F);
- break;
- }
- m.command = (m.command < std::size(imfEffects)) ? imfEffects[m.command] : CMD_NONE;
- if(m.command == CMD_VOLUME && m.volcmd == VOLCMD_NONE)
- {
- m.volcmd = VOLCMD_VOLUME;
- m.vol = m.param;
- m.command = CMD_NONE;
- m.param = 0;
- }
- }
- static bool ValidateHeader(const IMFFileHeader &fileHeader)
- {
- if(std::memcmp(fileHeader.im10, "IM10", 4)
- || fileHeader.ordNum > 256
- || fileHeader.insNum >= MAX_INSTRUMENTS
- || fileHeader.bpm < 32
- || fileHeader.master > 64
- || fileHeader.amp < 4
- || fileHeader.amp > 127)
- {
- return false;
- }
- bool channelFound = false;
- for(const auto &chn : fileHeader.channels)
- {
- switch(chn.status)
- {
- case 0: // enabled; don't worry about it
- channelFound = true;
- break;
- case 1: // mute
- channelFound = true;
- break;
- case 2: // disabled
- // nothing
- break;
- default: // uhhhh.... freak out
- return false;
- }
- }
- if(!channelFound)
- {
- return false;
- }
- return true;
- }
- static uint64 GetHeaderMinimumAdditionalSize(const IMFFileHeader &fileHeader)
- {
- return 256 + fileHeader.patNum * 4 + fileHeader.insNum * sizeof(IMFInstrument);
- }
- CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderIMF(MemoryFileReader file, const uint64 *pfilesize)
- {
- IMFFileHeader fileHeader;
- if(!file.ReadStruct(fileHeader))
- {
- return ProbeWantMoreData;
- }
- if(!ValidateHeader(fileHeader))
- {
- return ProbeFailure;
- }
- return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
- }
- bool CSoundFile::ReadIMF(FileReader &file, ModLoadingFlags loadFlags)
- {
- IMFFileHeader fileHeader;
- file.Rewind();
- if(!file.ReadStruct(fileHeader))
- {
- return false;
- }
- if(!ValidateHeader(fileHeader))
- {
- return false;
- }
- if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
- {
- return false;
- }
- if(loadFlags == onlyVerifyHeader)
- {
- return true;
- }
- // Read channel configuration
- std::bitset<32> ignoreChannels; // bit set for each channel that's completely disabled
- uint8 detectedChannels = 0;
- for(uint8 chn = 0; chn < 32; chn++)
- {
- ChnSettings[chn].Reset();
- ChnSettings[chn].nPan = fileHeader.channels[chn].panning * 256 / 255;
- ChnSettings[chn].szName = mpt::String::ReadBuf(mpt::String::nullTerminated, fileHeader.channels[chn].name);
- // TODO: reverb/chorus?
- switch(fileHeader.channels[chn].status)
- {
- case 0: // enabled; don't worry about it
- detectedChannels = chn + 1;
- break;
- case 1: // mute
- ChnSettings[chn].dwFlags = CHN_MUTE;
- detectedChannels = chn + 1;
- break;
- case 2: // disabled
- ChnSettings[chn].dwFlags = CHN_MUTE;
- ignoreChannels[chn] = true;
- break;
- default: // uhhhh.... freak out
- return false;
- }
- }
- InitializeGlobals(MOD_TYPE_IMF);
- m_nChannels = detectedChannels;
- m_modFormat.formatName = U_("Imago Orpheus");
- m_modFormat.type = U_("imf");
- m_modFormat.charset = mpt::Charset::CP437;
- //From mikmod: work around an Orpheus bug
- if(fileHeader.channels[0].status == 0)
- {
- CHANNELINDEX chn;
- for(chn = 1; chn < 16; chn++)
- if(fileHeader.channels[chn].status != 1)
- break;
- if(chn == 16)
- for(chn = 1; chn < 16; chn++)
- ChnSettings[chn].dwFlags.reset(CHN_MUTE);
- }
- // Song Name
- m_songName = mpt::String::ReadBuf(mpt::String::nullTerminated, fileHeader.title);
- m_SongFlags.set(SONG_LINEARSLIDES, fileHeader.flags & IMFFileHeader::linearSlides);
- m_nDefaultSpeed = fileHeader.tempo;
- m_nDefaultTempo.Set(fileHeader.bpm);
- m_nDefaultGlobalVolume = fileHeader.master * 4u;
- m_nSamplePreAmp = fileHeader.amp;
- m_nInstruments = fileHeader.insNum;
- m_nSamples = 0; // Will be incremented later
- uint8 orders[256];
- file.ReadArray(orders);
- ReadOrderFromArray(Order(), orders, fileHeader.ordNum, uint16_max, 0xFF);
- // Read patterns
- if(loadFlags & loadPatternData)
- Patterns.ResizeArray(fileHeader.patNum);
- for(PATTERNINDEX pat = 0; pat < fileHeader.patNum; pat++)
- {
- const uint16 length = file.ReadUint16LE(), numRows = file.ReadUint16LE();
- FileReader patternChunk = file.ReadChunk(length - 4);
- if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, numRows))
- {
- continue;
- }
- ModCommand dummy;
- ROWINDEX row = 0;
- while(row < numRows)
- {
- uint8 mask = patternChunk.ReadUint8();
- if(mask == 0)
- {
- row++;
- continue;
- }
- uint8 channel = mask & 0x1F;
- ModCommand &m = (channel < GetNumChannels()) ? *Patterns[pat].GetpModCommand(row, channel) : dummy;
- if(mask & 0x20)
- {
- // Read note/instrument
- const auto [note, instr] = patternChunk.ReadArray<uint8, 2>();
- m.note = note;
- m.instr = instr;
- if(m.note == 160)
- {
- m.note = NOTE_KEYOFF;
- } else if(m.note == 255)
- {
- m.note = NOTE_NONE;
- } else
- {
- m.note = (m.note >> 4) * 12 + (m.note & 0x0F) + 12 + 1;
- if(!m.IsNoteOrEmpty())
- {
- m.note = NOTE_NONE;
- }
- }
- }
- if((mask & 0xC0) == 0xC0)
- {
- // Read both effects and figure out what to do with them
- const auto [e1c, e1d, e2c, e2d] = patternChunk.ReadArray<uint8, 4>(); // Command 1, Data 1, Command 2, Data 2
- if(e1c == 0x0C)
- {
- m.vol = std::min(e1d, uint8(0x40));
- m.volcmd = VOLCMD_VOLUME;
- m.command = e2c;
- m.param = e2d;
- } else if(e2c == 0x0C)
- {
- m.vol = std::min(e2d, uint8(0x40));
- m.volcmd = VOLCMD_VOLUME;
- m.command = e1c;
- m.param = e1d;
- } else if(e1c == 0x0A)
- {
- m.vol = e1d * 64 / 255;
- m.volcmd = VOLCMD_PANNING;
- m.command = e2c;
- m.param = e2d;
- } else if(e2c == 0x0A)
- {
- m.vol = e2d * 64 / 255;
- m.volcmd = VOLCMD_PANNING;
- m.command = e1c;
- m.param = e1d;
- } else
- {
- /* check if one of the effects is a 'global' effect
- -- if so, put it in some unused channel instead.
- otherwise pick the most important effect. */
- m.command = e2c;
- m.param = e2d;
- }
- } else if(mask & 0xC0)
- {
- // There's one effect, just stick it in the effect column
- const auto [command, param] = patternChunk.ReadArray<uint8, 2>();
- m.command = command;
- m.param = param;
- }
- if(m.command)
- ImportIMFEffect(m);
- if(ignoreChannels[channel] && m.IsGlobalCommand())
- m.command = CMD_NONE;
- }
- }
- SAMPLEINDEX firstSample = 1; // first sample index of the current instrument
- // read instruments
- for(INSTRUMENTINDEX ins = 0; ins < GetNumInstruments(); ins++)
- {
- ModInstrument *instr = AllocateInstrument(ins + 1);
- IMFInstrument instrumentHeader;
- if(!file.ReadStruct(instrumentHeader) || instr == nullptr)
- {
- continue;
- }
- // Orpheus does not check this!
- //if(memcmp(instrumentHeader.ii10, "II10", 4) != 0)
- // return false;
- instrumentHeader.ConvertToMPT(*instr, firstSample);
- // Read this instrument's samples
- for(SAMPLEINDEX smp = 0; smp < instrumentHeader.smpNum; smp++)
- {
- IMFSample sampleHeader;
- file.ReadStruct(sampleHeader);
- const SAMPLEINDEX smpID = firstSample + smp;
- if(memcmp(sampleHeader.is10, "IS10", 4) || smpID >= MAX_SAMPLES)
- {
- continue;
- }
- m_nSamples = smpID;
- ModSample &sample = Samples[smpID];
- sampleHeader.ConvertToMPT(sample);
- m_szNames[smpID] = sample.filename;
- if(sampleHeader.length)
- {
- FileReader sampleChunk = file.ReadChunk(sampleHeader.length);
- if(loadFlags & loadSampleData)
- {
- SampleIO(
- sample.uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,
- SampleIO::mono,
- SampleIO::littleEndian,
- SampleIO::signedPCM)
- .ReadSample(sample, sampleChunk);
- }
- }
- }
- firstSample += instrumentHeader.smpNum;
- }
- return true;
- }
- OPENMPT_NAMESPACE_END
|