ref: 2fd0e3c541c991a83f21380c9c2a0e969d50fe69
dir: /src/modloaders/pt2_load_mod31.c/
/* 31-sample MOD loader (Amiga, PC, etc.) ** ** Note: Data sanitation is done in the last stage ** of module loading, so you don't need to do that here. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> // isdigit() #include <stdint.h> #include <stdbool.h> #include "../pt2_header.h" #include "../pt2_paula.h" // PAULA_VOICES #include "../pt2_config.h" #include "../pt2_structs.h" #include "../pt2_replayer.h" #include "../pt2_textout.h" #include "../pt2_visuals.h" enum // 31-sample .MOD types { FORMAT_UNKNOWN, FORMAT_PT, // ProTracker or compatible FORMAT_FLT, // Startrekker (4 channels) FORMAT_FT2, // FT2 (or other trackers, multichannel) FORMAT_NT, // NoiseTracker FORMAT_HMNT // His Master's NoiseTracker (special one) }; static int32_t realSampleLengths[MOD_SAMPLES]; static uint8_t getMod31Type(uint8_t *buffer, uint32_t filesize, uint8_t *numChannels); // 0 = not detected bool detectMod31(uint8_t *buffer, uint32_t filesize); module_t *loadMod31(uint8_t *buffer, uint32_t filesize) { module_t *m = createEmptyMod(); if (m == NULL) { statusOutOfMemory(); goto loadError; } uint8_t numChannels; uint8_t modFormat = getMod31Type(buffer, filesize, &numChannels); if (modFormat == FORMAT_UNKNOWN || numChannels > PAULA_VOICES) { displayErrorMsg("UNSUPPORTED MOD !"); goto loadError; } uint8_t *p = buffer; memcpy(m->header.name, p, 20); p += 20; // read sample headers moduleSample_t *s = m->samples; for (int32_t i = 0; i < MOD_SAMPLES; i++, s++) { memcpy(s->text, p, 22); p += 22; realSampleLengths[i] = ((p[0] << 8) | p[1]) * 2; p += 2; s->length = (realSampleLengths[i] > config.maxSampleLength) ? config.maxSampleLength : realSampleLengths[i]; s->fineTune = *p++ & 0xF; s->volume = *p++; s->loopStart = ((p[0] << 8) | p[1]) * 2; p += 2; s->loopLength = ((p[0] << 8) | p[1]) * 2; p += 2; // fix for poorly converted STK (< v2.5) -> PT/NT modules if (s->loopLength > 2 && s->loopStart+s->loopLength > s->length) { if ((s->loopStart/2) + s->loopLength <= s->length) s->loopStart /= 2; } } m->header.songLength = *p++; p++; // skip uninteresting field (127 in PT MOds, "restart pos" in PC MODs) if (modFormat == FORMAT_PT && m->header.songLength == 129) m->header.songLength = 127; // fixes a specific copy of beatwave.mod (XXX: hackish...) if (m->header.songLength == 0 || m->header.songLength > 129) { displayErrorMsg("NOT A MOD FILE !"); goto loadError; } // read orders and count number of patterns int32_t numPatterns = 0; for (int32_t i = 0; i < 128; i++) { m->header.patternTable[i] = *p++; if (m->header.patternTable[i] > numPatterns) numPatterns = m->header.patternTable[i]; } numPatterns++; if (numPatterns > MAX_PATTERNS) { displayErrorMsg("UNSUPPORTED MOD !"); goto loadError; } p += 4; // skip magic ID (already handled) // load pattern data for (int32_t i = 0; i < numPatterns; i++) { note_t *note = m->patterns[i]; for (int32_t j = 0; j < MOD_ROWS; j++) { for (int32_t k = 0; k < numChannels; k++, note++, p += 4) { note->period = ((p[0] & 0x0F) << 8) | p[1]; note->sample = ((p[0] & 0xF0) | (p[2] >> 4)) & 31; note->command = p[2] & 0x0F; note->param = p[3]; } if (numChannels < PAULA_VOICES) note += PAULA_VOICES-numChannels; } } if (modFormat != FORMAT_PT) // pattern command conversion for non-PT formats { for (int32_t i = 0; i < numPatterns; i++) { note_t *note = m->patterns[i]; for (int32_t j = 0; j < MOD_ROWS*4; j++, note++) { if (modFormat == FORMAT_NT || modFormat == FORMAT_HMNT) { // any Dxx == D00 in NT/HMNT if (note->command == 0xD) note->param = 0; // effect F with param 0x00 does nothing in NT/HMNT if (note->command == 0xF && note->param == 0) note->command = 0; } else if (modFormat == FORMAT_FLT) // Startrekker (4 channels) { if (note->command == 0xE) // remove unsupported "assembly macros" command { note->command = 0; note->param = 0; } // Startrekker is always in vblank mode, and limits speed to 0x1F if (note->command == 0xF && note->param > 0x1F) note->param = 0x1F; } // remove E8x (Karplus-Strong is only supported for ProTracker .MODs) if (note->command == 0xE && (note->param >> 4) == 0x8) { note->command = 0; note->param = 0; } // remove EFx (Invert Loop is only supported for ProTracker .MODs) if (note->command == 0xE && (note->param >> 4) == 0xF) { note->command = 0; note->param = 0; } } } } // load sample data s = m->samples; for (int32_t i = 0; i < MOD_SAMPLES; i++, s++) { int32_t bytesToSkip = 0; if (realSampleLengths[i] > config.maxSampleLength) bytesToSkip = realSampleLengths[i] - config.maxSampleLength; memcpy(&m->sampleData[s->offset], p, s->length); p += s->length; if (bytesToSkip > 0) p += bytesToSkip; } m->header.initialTempo = 125; return m; loadError: if (m != NULL) { for (int32_t i = 0; i < MAX_PATTERNS; i++) { if (m->patterns[i] != NULL) free(m->patterns[i]); } if (m->sampleData != NULL) free(m->sampleData); free(m); } return NULL; } static uint8_t getMod31Type(uint8_t *buffer, uint32_t filesize, uint8_t *numChannels) // 0 = not detected { const uint8_t *id = &buffer[1080]; #define ID(s) !memcmp(s, id, 4) *numChannels = 0; if (buffer == NULL || filesize < 1084+1024) return FORMAT_UNKNOWN; *numChannels = 4; if (ID("M.K.") || ID("M!K!") || ID("NSMS") || ID("LARD") || ID("PATT")) { return FORMAT_PT; // ProTracker (or compatible) } else if (ID("FLT4")) { return FORMAT_FLT; // Startrekker (4 channels) } else if (ID("N.T.")) { return FORMAT_NT; // NoiseTracker } else if (ID("M&K!") || ID("FEST")) { return FORMAT_HMNT; // His Master's NoiseTracker } else if (isdigit(id[0]) && (id[1] == 'C' && id[2] == 'H' && id[3] == 'N')) { *numChannels = id[0] - '0'; return FORMAT_FT2; // Fasttracker II 1..9 channels (or other trackers) } else if (isdigit(id[0]) && isdigit(id[1]) && id[1] == 'C' && id[2] == 'H') { *numChannels = ((id[0] - '0') * 10) + (id[1] - '0'); return FORMAT_FT2; // Fasttracker II 10+ channels (or other trackers) } return FORMAT_UNKNOWN; } bool detectMod31(uint8_t *buffer, uint32_t filesize) { uint8_t junk; if (buffer == NULL || filesize < 1084+1024) return FORMAT_UNKNOWN; return getMod31Type(buffer, filesize, &junk) != FORMAT_UNKNOWN; }