| 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
 
 
  |