123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499 |
- /*
- * Load_okt.cpp
- * ------------
- * Purpose: OKT (Oktalyzer) module loader
- * Notes : (currently none)
- * 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 OktIffChunk
- {
- // IFF chunk names
- enum ChunkIdentifiers
- {
- idCMOD = MagicBE("CMOD"),
- idSAMP = MagicBE("SAMP"),
- idSPEE = MagicBE("SPEE"),
- idSLEN = MagicBE("SLEN"),
- idPLEN = MagicBE("PLEN"),
- idPATT = MagicBE("PATT"),
- idPBOD = MagicBE("PBOD"),
- idSBOD = MagicBE("SBOD"),
- };
- uint32be signature; // IFF chunk name
- uint32be chunksize; // chunk size without header
- };
- MPT_BINARY_STRUCT(OktIffChunk, 8)
- struct OktSample
- {
- char name[20];
- uint32be length; // length in bytes
- uint16be loopStart; // *2 for real value
- uint16be loopLength; // ditto
- uint16be volume; // default volume
- uint16be type; // 7-/8-bit sample
- };
- MPT_BINARY_STRUCT(OktSample, 32)
- // Parse the sample header block
- static void ReadOKTSamples(FileReader &chunk, CSoundFile &sndFile)
- {
- sndFile.m_nSamples = std::min(static_cast<SAMPLEINDEX>(chunk.BytesLeft() / sizeof(OktSample)), static_cast<SAMPLEINDEX>(MAX_SAMPLES - 1));
- for(SAMPLEINDEX smp = 1; smp <= sndFile.GetNumSamples(); smp++)
- {
- ModSample &mptSmp = sndFile.GetSample(smp);
- OktSample oktSmp;
- chunk.ReadStruct(oktSmp);
- mptSmp.Initialize();
- sndFile.m_szNames[smp] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, oktSmp.name);
- mptSmp.nC5Speed = 8287;
- mptSmp.nVolume = std::min(oktSmp.volume.get(), uint16(64)) * 4u;
- mptSmp.nLength = oktSmp.length & ~1;
- mptSmp.cues[0] = oktSmp.type; // Temporary storage for pattern reader, will be reset later
- // Parse loops
- const SmpLength loopStart = oktSmp.loopStart * 2;
- const SmpLength loopLength = oktSmp.loopLength * 2;
- if(loopLength > 2 && loopStart + loopLength <= mptSmp.nLength)
- {
- mptSmp.uFlags.set(CHN_SUSTAINLOOP);
- mptSmp.nSustainStart = loopStart;
- mptSmp.nSustainEnd = loopStart + loopLength;
- }
- }
- }
- // Parse a pattern block
- static void ReadOKTPattern(FileReader &chunk, PATTERNINDEX pat, CSoundFile &sndFile, const std::array<int8, 8> pairedChn)
- {
- if(!chunk.CanRead(2))
- {
- // Invent empty pattern
- sndFile.Patterns.Insert(pat, 64);
- return;
- }
- ROWINDEX rows = Clamp(static_cast<ROWINDEX>(chunk.ReadUint16BE()), ROWINDEX(1), MAX_PATTERN_ROWS);
- if(!sndFile.Patterns.Insert(pat, rows))
- {
- return;
- }
- const CHANNELINDEX chns = sndFile.GetNumChannels();
- for(ROWINDEX row = 0; row < rows; row++)
- {
- auto rowCmd = sndFile.Patterns[pat].GetRow(row);
- for(CHANNELINDEX chn = 0; chn < chns; chn++)
- {
- ModCommand &m = rowCmd[chn];
- const auto [note, instr, effect, param] = chunk.ReadArray<uint8, 4>();
- if(note > 0 && note <= 36)
- {
- m.note = note + (NOTE_MIDDLEC - 13);
- m.instr = instr + 1;
- if(m.instr > 0 && m.instr <= sndFile.GetNumSamples())
- {
- const auto &sample = sndFile.GetSample(m.instr);
- // Default volume only works on raw Paula channels
- if(pairedChn[chn] && sample.nVolume < 256)
- {
- m.volcmd = VOLCMD_VOLUME;
- m.vol = 64;
- }
- // If channel and sample type don't match, stop this channel (add 100 to the instrument number to make it understandable what happened during import)
- if((sample.cues[0] == 1 && pairedChn[chn] != 0) || (sample.cues[0] == 0 && pairedChn[chn] == 0))
- {
- m.instr += 100;
- }
- }
- }
- switch(effect)
- {
- case 0: // Nothing
- break;
- case 1: // 1 Portamento Down (Period)
- if(param)
- {
- m.command = CMD_PORTAMENTOUP;
- m.param = param;
- }
- break;
- case 2: // 2 Portamento Up (Period)
- if(param)
- {
- m.command = CMD_PORTAMENTODOWN;
- m.param = param;
- }
- break;
- #if 0
- /* these aren't like regular arpeggio: "down" means to *subtract* the offset from the note.
- For now I'm going to leave these unimplemented. */
- case 10: // A Arpeggio 1 (down, orig, up)
- case 11: // B Arpeggio 2 (orig, up, orig, down)
- if(param)
- {
- m.command = CMD_ARPEGGIO;
- m.param = param;
- }
- break;
- #endif
- // This one is close enough to "standard" arpeggio -- I think!
- case 12: // C Arpeggio 3 (up, up, orig)
- if(param)
- {
- m.command = CMD_ARPEGGIO;
- m.param = param;
- }
- break;
- case 13: // D Slide Down (Notes)
- if(param)
- {
- m.command = CMD_NOTESLIDEDOWN;
- m.param = 0x10 | std::min(uint8(0x0F), param);
- }
- break;
- case 30: // U Slide Up (Notes)
- if(param)
- {
- m.command = CMD_NOTESLIDEUP;
- m.param = 0x10 | std::min(uint8(0x0F), param);
- }
- break;
- // Fine Slides are only implemented for libopenmpt. For OpenMPT,
- // sliding every 5 (non-note) ticks kind of works (at least at
- // speed 6), but implementing separate (format-agnostic) fine slide commands would of course be better.
- case 21: // L Slide Down Once (Notes)
- if(param)
- {
- m.command = CMD_NOTESLIDEDOWN;
- m.param = 0x50 | std::min(uint8(0x0F), param);
- }
- break;
- case 17: // H Slide Up Once (Notes)
- if(param)
- {
- m.command = CMD_NOTESLIDEUP;
- m.param = 0x50 | std::min(uint8(0x0F), param);
- }
- break;
- case 15: // F Set Filter <>00:ON
- m.command = CMD_MODCMDEX;
- m.param = !!param;
- break;
- case 25: // P Pos Jump
- m.command = CMD_POSITIONJUMP;
- m.param = param;
- break;
- case 27: // R Release sample (apparently not listed in the help!)
- m.Clear();
- m.note = NOTE_KEYOFF;
- break;
- case 28: // S Speed
- if(param < 0x20)
- {
- m.command = CMD_SPEED;
- m.param = param;
- }
- break;
- case 31: // V Volume
- // Volume on mixed channels is permanent, on hardware channels it behaves like in regular MODs
- if(param & 0x0F)
- {
- m.command = pairedChn[chn] ? CMD_CHANNELVOLSLIDE : CMD_VOLUMESLIDE;
- m.param = param & 0x0F;
- }
- switch(param >> 4)
- {
- case 4: // Normal slide down
- if(param != 0x40)
- break;
- // 0x40 is set volume -- fall through
- [[fallthrough]];
- case 0: case 1: case 2: case 3:
- if(pairedChn[chn])
- {
- m.command = CMD_CHANNELVOLUME;
- m.param = param;
- } else
- {
- m.volcmd = VOLCMD_VOLUME;
- m.vol = param;
- m.command = CMD_NONE;
- }
- break;
- case 5: // Normal slide up
- m.param <<= 4;
- break;
- case 6: // Fine slide down
- m.param = 0xF0 | std::min(static_cast<uint8>(m.param), uint8(0x0E));
- break;
- case 7: // Fine slide up
- m.param = (std::min(static_cast<uint8>(m.param), uint8(0x0E)) << 4) | 0x0F;
- break;
- default:
- // Junk.
- m.command = CMD_NONE;
- break;
- }
- // Volume is shared between two mixed channels, second channel has priority
- if(m.command == CMD_CHANNELVOLUME || m.command == CMD_CHANNELVOLSLIDE)
- {
- ModCommand &other = rowCmd[chn + pairedChn[chn]];
- // Try to preserve effect if there already was one
- if(other.ConvertVolEffect(other.command, other.param, true))
- {
- other.volcmd = other.command;
- other.vol = other.param;
- }
- other.command = m.command;
- other.param = m.param;
- }
- break;
- #if 0
- case 24: // O Old Volume (???)
- m.command = CMD_VOLUMESLIDE;
- m.param = 0;
- break;
- #endif
- default:
- m.command = CMD_NONE;
- break;
- }
- }
- }
- }
- CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderOKT(MemoryFileReader file, const uint64 *pfilesize)
- {
- if(!file.CanRead(8))
- {
- return ProbeWantMoreData;
- }
- if(!file.ReadMagic("OKTASONG"))
- {
- return ProbeFailure;
- }
- OktIffChunk iffHead;
- if(!file.ReadStruct(iffHead))
- {
- return ProbeWantMoreData;
- }
- if(iffHead.chunksize == 0)
- {
- return ProbeFailure;
- }
- if((iffHead.signature & 0x80808080u) != 0) // ASCII?
- {
- return ProbeFailure;
- }
- MPT_UNREFERENCED_PARAMETER(pfilesize);
- return ProbeSuccess;
- }
- bool CSoundFile::ReadOKT(FileReader &file, ModLoadingFlags loadFlags)
- {
- file.Rewind();
- if(!file.ReadMagic("OKTASONG"))
- {
- return false;
- }
- // prepare some arrays to store offsets etc.
- std::vector<FileReader> patternChunks;
- std::vector<FileReader> sampleChunks;
- std::array<int8, 8> pairedChn{{}};
- ORDERINDEX numOrders = 0;
- InitializeGlobals(MOD_TYPE_OKT);
- m_modFormat.formatName = U_("Oktalyzer");
- m_modFormat.type = U_("okt");
- m_modFormat.charset = mpt::Charset::Amiga_no_C1;
- // Go through IFF chunks...
- while(file.CanRead(sizeof(OktIffChunk)))
- {
- OktIffChunk iffHead;
- if(!file.ReadStruct(iffHead))
- {
- break;
- }
- FileReader chunk = file.ReadChunk(iffHead.chunksize);
- if(!chunk.IsValid())
- {
- break;
- }
- switch(iffHead.signature)
- {
- case OktIffChunk::idCMOD:
- // Channel setup table
- if(m_nChannels == 0 && chunk.GetLength() >= 8)
- {
- const auto chnTable = chunk.ReadArray<uint16be, 4>();
- for(CHANNELINDEX chn = 0; chn < 4; chn++)
- {
- if(chnTable[chn])
- {
- pairedChn[m_nChannels] = 1;
- pairedChn[m_nChannels + 1] = -1;
- ChnSettings[m_nChannels].Reset();
- ChnSettings[m_nChannels++].nPan = (((chn & 3) == 1) || ((chn & 3) == 2)) ? 0xC0 : 0x40;
- }
- ChnSettings[m_nChannels].Reset();
- ChnSettings[m_nChannels++].nPan = (((chn & 3) == 1) || ((chn & 3) == 2)) ? 0xC0 : 0x40;
- }
- if(loadFlags == onlyVerifyHeader)
- {
- return true;
- }
- }
- break;
- case OktIffChunk::idSAMP:
- // Convert sample headers
- if(m_nSamples > 0)
- {
- break;
- }
- ReadOKTSamples(chunk, *this);
- break;
- case OktIffChunk::idSPEE:
- // Read default speed
- if(chunk.GetLength() >= 2)
- {
- m_nDefaultSpeed = Clamp(chunk.ReadUint16BE(), uint16(1), uint16(255));
- }
- break;
- case OktIffChunk::idSLEN:
- // Number of patterns, we don't need this.
- break;
- case OktIffChunk::idPLEN:
- // Read number of valid orders
- if(chunk.GetLength() >= 2)
- {
- numOrders = chunk.ReadUint16BE();
- }
- break;
- case OktIffChunk::idPATT:
- // Read the orderlist
- ReadOrderFromFile<uint8>(Order(), chunk, chunk.GetLength(), 0xFF, 0xFE);
- break;
- case OktIffChunk::idPBOD:
- // Don't read patterns for now, as the number of channels might be unknown at this point.
- if(patternChunks.size() < 256)
- {
- patternChunks.push_back(chunk);
- }
- break;
- case OktIffChunk::idSBOD:
- // Sample data - same as with patterns, as we need to know the sample format / length
- if(sampleChunks.size() < MAX_SAMPLES - 1 && chunk.GetLength() > 0)
- {
- sampleChunks.push_back(chunk);
- }
- break;
- default:
- // Non-ASCII chunk ID?
- if(iffHead.signature & 0x80808080)
- return false;
- break;
- }
- }
- // If there wasn't even a CMOD chunk, we can't really load this.
- if(m_nChannels == 0)
- return false;
- m_nDefaultTempo.Set(125);
- m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME;
- m_nSamplePreAmp = m_nVSTiVolume = 48;
- m_nMinPeriod = 113 * 4;
- m_nMaxPeriod = 856 * 4;
- // Fix orderlist
- Order().resize(numOrders);
- // Read patterns
- if(loadFlags & loadPatternData)
- {
- Patterns.ResizeArray(static_cast<PATTERNINDEX>(patternChunks.size()));
- for(PATTERNINDEX pat = 0; pat < patternChunks.size(); pat++)
- {
- ReadOKTPattern(patternChunks[pat], pat, *this, pairedChn);
- }
- }
- // Read samples
- size_t fileSmp = 0;
- for(SAMPLEINDEX smp = 1; smp < m_nSamples; smp++)
- {
- if(fileSmp >= sampleChunks.size() || !(loadFlags & loadSampleData))
- break;
- ModSample &mptSample = Samples[smp];
- mptSample.SetDefaultCuePoints();
- if(mptSample.nLength == 0)
- continue;
- // Weird stuff?
- LimitMax(mptSample.nLength, mpt::saturate_cast<SmpLength>(sampleChunks[fileSmp].GetLength()));
- SampleIO(
- SampleIO::_8bit,
- SampleIO::mono,
- SampleIO::bigEndian,
- SampleIO::signedPCM)
- .ReadSample(mptSample, sampleChunks[fileSmp]);
- fileSmp++;
- }
- return true;
- }
- OPENMPT_NAMESPACE_END
|