// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. // See LICENSE.md for license information. // SPDX-License-Identifier: MIT // // modToNtp.c - a Protracker module (.MOD) as a NinjaTrackerPlus song (.NTP, // version 2), the form the IIgs port's replayer plays (tools/joeymod's // .ntp output). // // modToNtp IN.MOD OUT.NTP // // Written from NinjaTrackerPlus' own documentation of the file // (file_format.txt) and its replayer's source; how a module's parts become // that file's was worked out by converting test modules with Ninjaforce's // converter and reading the results, so this makes the same files. The // rules: // // Samples become instruments, empty ones (no length) left out and the // rest numbered in order. Their bytes are made the DOC's: unsigned (each // gains 128), and 0, which stops a DOC oscillator, made 1. An instrument // whose length is a power of two from 256 on is ideal (its table is // exactly it); any other is followed by 8 zero bytes that stop the // oscillator, and its table is the smallest power of two from 256 that // holds it. A sample with no loop (a repeat length of 2 bytes or less) is // one instrument. A looped one (the loop cut back to the sample's end): // - starting at byte 0 or 2 is one instrument of its loop alone, made to // play long enough: a loop under 256 bytes is repeated, to 512 when its // length is a power of two (ideal), else as many times as fit in 504; // - starting later is a header (the sample up to the loop's quietest // byte, the first nearest the DOC's centre) and a loop instrument (the // loop from that byte round to it, repeated as above), played by two // oscillators in turn. // An instrument past 32760 bytes, one played with a sample offset (effect // 9), or one that DOC RAM cannot hold is streamed from memory through a // 512-byte buffer for each track that plays it: the sample whole (8 // stopper bytes after it), or a looped one up to its loop, the loop laid // down as many times as it takes to fill a buffer, and the loop's first // 256 bytes again (ideal when that comes to a power of two). When the // instruments do not fit, the longest sample not streamed yet is // streamed, and the whole placement is tried again. // // DOC RAM is 256 pages. The instruments in order, then a buffer for each // track that plays a streamed sample (sample by sample, the tracks playing // it in the order they first play anything), are sorted most pages first, // step for step as PHP 8 sorts them: Ninjaforce's converter is PHP, and it // compares a buffer and an instrument of equal pages so that each goes // after the other, which leaves their order to the sort's workings. Each // is placed at the lowest address its table size divides where its pages // are free. Page 0 is the replayer's interrupt timer's, which must never // read a 0: it takes only an instrument filling it. // // A track plays through two oscillators when it plays any instrument that // needs two (a streamed one, a header and loop, or a loop that is not // ideal), else one; track by track, it gets the lowest free oscillator, or // the lowest free even and odd pair. Tracks sound on the right, left, // left, right in fours. Notes become NTP's note numbers (1 is C-0, period // 1712; 72 is B-5) by period, each note taking the periods from its own // down to half way to the next; effects pass unchanged. // // A module whose sample's loop starts at or past its end is converted with // that sample unlooped (Ninjaforce's converter stops on it); M!K!, CD81, // OKTA, and OCTA modules are read as the 4- and 8-channel modules they // are. #include #include #include #include "toolBuf.h" #include "toolFile.h" #define TOOL_NAME "modToNtp" #define USAGE_EXIT 2 // The module. #define MOD_NAME_BYTES 20u #define MOD_SAMPLES 31u #define MOD_SAMPLE_AT 20u #define MOD_SAMPLE_BYTES 30u #define MOD_SAMPLE_NAME 22u #define MOD_LENGTH_AT 22u #define MOD_FINE_AT 24u #define MOD_VOLUME_AT 25u #define MOD_REP_AT 26u #define MOD_REPLEN_AT 28u #define MOD_ORDER_COUNT_AT 950u #define MOD_ORDER_AT 952u #define MOD_ORDER_MAX 128u #define MOD_MAGIC_AT 1080u #define MOD_MAGIC_BYTES 4u #define MOD_HEADER_BYTES 1084u #define MOD_ROWS 64u #define MOD_CELL_BYTES 4u #define MOD_TRACKS_MAX 8u #define FINE_MASK 0x0Fu #define SAMPLE_HIGH_MASK 0xF0u #define PERIOD_HIGH_MASK 0x0Fu #define EFFECT_MASK 0x0Fu #define EFFECT_OFFSET 0x09u #define NIBBLE_BITS 4u #define BYTE_BITS 8u // A loop of 2 bytes or less is none; one starting before byte 4 has no // header. #define LOOP_MIN 2u #define HEADER_START_MIN 4u // The DOC. #define DOC_BYTES 65536u #define PAGE_BYTES 256u #define DOC_PAGES (DOC_BYTES / PAGE_BYTES) #define DOC_CENTRE 128u #define DOC_STOP 0u #define DOC_STOP_FIX 1u #define STOPPER_BYTES 8u #define TABLE_MIN 256u #define WAVE_SIZE_SCALE 9u #define STREAM_MAX 32760u #define STREAM_BUFFER 512u #define STREAM_LOOP_COPY 256u // A loop under LOOP_REPEAT_UNDER bytes is repeated: to LOOP_IDEAL when it // is a power of two, else as many times as fit in LOOP_FILL. #define LOOP_REPEAT_UNDER 256u #define LOOP_IDEAL 512u #define LOOP_FILL (LOOP_IDEAL - STOPPER_BYTES) // NTP. #define NTP_MAGIC "nfc!" #define NTP_MAGIC_BYTES 4u #define NTP_VERSION 2u #define TYPE_STREAMED 0x08u #define TYPE_IDEAL 0x04u #define TYPE_PLAIN 0x00u #define TYPE_LOOPED 0x01u #define TYPE_HEADER 0x02u #define TYPE_LOOP 0x03u #define OSC_TWO 0x80u #define CHANNEL_LEFT 0u #define CHANNEL_RIGHT 1u #define NOTES 72u #define OSCS_MAX (MOD_TRACKS_MAX * 2u) // Placement: the claims on DOC RAM (at most two instruments a sample and a // buffer a track), sorted as PHP sorts: by insertion up to SORT_SMALL of // them (under SORT_LINEAR, the first SORT_NETWORK by a fixed network; // else the first SORT_LINEAR a place at a time, the rest two). #define ITEMS_MAX (MOD_SAMPLES * 2u + MOD_TRACKS_MAX) #define SORT_SMALL 16u #define SORT_LINEAR 6u #define SORT_NETWORK 3u // A module's sample: what the module says of it, its bytes (those past the // file's end read as 0), and how it converts. typedef struct SampleT { char name[MOD_SAMPLE_NAME + 1u]; uint32_t length; uint32_t rep; uint32_t repLen; uint8_t fine; uint8_t volume; const uint8_t *data; uint32_t have; bool looped; bool streamed; uint32_t first; } SampleT; // An NTP instrument: its type, its DOC bytes, its table, and where it was // placed. typedef struct InstT { const SampleT *sample; uint8_t type; ToolBufT bytes; uint32_t table; uint32_t address; } InstT; // A claim on DOC RAM: an instrument's bytes or a track's stream buffer, // its table size, and where its address goes. typedef struct ClaimT { uint32_t size; uint32_t table; uint32_t *where; bool buffer; } ClaimT; typedef struct ModT { const uint8_t *file; size_t len; char name[MOD_NAME_BYTES + 1u]; uint32_t tracks; uint32_t patterns; uint32_t orders; const uint8_t *orderTable; const uint8_t *cells; SampleT samples[MOD_SAMPLES]; InstT insts[MOD_SAMPLES * 2u]; uint32_t instCount; bool twoOsc[MOD_TRACKS_MAX]; uint32_t plays[MOD_TRACKS_MAX]; uint32_t trackOrder[MOD_TRACKS_MAX]; uint32_t tracksSeen; uint32_t buffer[MOD_TRACKS_MAX]; } ModT; static void addLoop(ToolBufT *out, const SampleT *s, uint32_t start, uint32_t loopLen, uint32_t rotate); static bool claimAfter(const ClaimT *c, const uint32_t *o, uint32_t a, uint32_t b); static uint8_t docByte(const SampleT *s, uint32_t at); static void finish(InstT *inst, uint8_t type); static void instAdd(ModT *m, const SampleT *s, uint8_t type); static void instruments(ModT *m); static bool isIdeal(size_t len); static bool isPowerOfTwo(uint32_t v); static uint32_t loopRepeats(uint32_t loopLen, bool streamed); static void modRead(ModT *m, const char *path); static bool needsTwo(const ModT *m, const SampleT *s); static uint32_t noteOf(uint32_t period); static void ntpWrite(const ModT *m, const char *path); static void orderSwap(uint32_t *o, uint32_t a, uint32_t b); static uint32_t pagesOf(uint32_t bytes); static uint32_t partition(const ClaimT *c, uint32_t *o, uint32_t n); static bool place(ModT *m); static void sinkLast(const ClaimT *c, uint32_t *o, uint32_t last); static void sort3(const ClaimT *c, uint32_t *o, uint32_t x, uint32_t y, uint32_t z); static void sortClaims(const ClaimT *c, uint32_t *o, uint32_t n); static void sortInsert(const ClaimT *c, uint32_t *o, uint32_t n); static uint32_t splitAt(const SampleT *s, uint32_t start, uint32_t loopLen); static void tracksScan(ModT *m); // The least period of each note but the last (note n takes the periods from // its own down to half way to note n + 1's). static const uint16_t kNoteFloor[NOTES - 1u] = { 1664, 1570, 1482, 1398, 1318, 1244, 1174, 1108, 1046, 988, 933, 881, 832, 785, 741, 699, 659, 622, 587, 554, 523, 494, 467, 441, 416, 393, 371, 350, 330, 311, 294, 277, 262, 247, 233, 220, 208, 196, 185, 175, 165, 156, 147, 139, 131, 124, 117, 110, 104, 98, 93, 88, 83, 78, 73, 69, 65, 62, 58, 55, 52, 49, 46, 44, 41, 39, 36, 34, 32, 31, 29 }; // A loop's bytes: loopLen of them from start, beginning rotate bytes in // and running round, as many times over as loopRepeats says. static void addLoop(ToolBufT *out, const SampleT *s, uint32_t start, uint32_t loopLen, uint32_t rotate) { uint32_t times; uint32_t t; uint32_t i; times = loopRepeats(loopLen, s->streamed); for (t = 0; t < times; t++) { for (i = 0; i < loopLen; i++) { toolBufByte(out, docByte(s, start + (rotate + i) % loopLen)); } } } // Whether the claim at order position a goes after the one at b: fewer // pages, else later in the list; but a buffer and an instrument of equal // pages each go after the other (Ninjaforce's converter compares them so), // which leaves their order to how sortClaims runs. static bool claimAfter(const ClaimT *c, const uint32_t *o, uint32_t a, uint32_t b) { uint32_t pagesA; uint32_t pagesB; pagesA = pagesOf(c[o[a]].size); pagesB = pagesOf(c[o[b]].size); if (pagesA != pagesB) { return pagesA < pagesB; } return c[o[a]].buffer != c[o[b]].buffer || o[a] > o[b]; } // A sample byte as the DOC's: unsigned, and never the 0 that stops it. static uint8_t docByte(const SampleT *s, uint32_t at) { uint8_t v; v = (uint8_t)((at < s->have ? s->data[at] : 0u) ^ DOC_CENTRE); return v == DOC_STOP ? DOC_STOP_FIX : v; } // An instrument's type, and its stoppers and table: ideal when its length // is a power of two from TABLE_MIN on, else the stoppers after it. static void finish(InstT *inst, uint8_t type) { uint32_t table; if (isIdeal(inst->bytes.len)) { type = (uint8_t)(type | TYPE_IDEAL); } else { toolBufZeros(&inst->bytes, STOPPER_BYTES); } inst->type = type; for (table = TABLE_MIN; table < inst->bytes.len; table *= 2u) { } inst->table = table; } static void instAdd(ModT *m, const SampleT *s, uint8_t type) { InstT *inst; inst = &m->insts[m->instCount++]; memset(inst, 0, sizeof(*inst)); inst->sample = s; inst->type = type; } // The instruments the samples make, as things stand (which are streamed). static void instruments(ModT *m) { SampleT *s; InstT *inst; uint32_t i; uint32_t k; uint32_t loopLen; uint32_t split; for (i = 0; i < m->instCount; i++) { toolBufFree(&m->insts[i].bytes); } m->instCount = 0u; for (i = 0; i < MOD_SAMPLES; i++) { s = &m->samples[i]; if (s->length == 0u) { continue; } s->first = m->instCount + 1u; loopLen = s->looped ? (s->rep + s->repLen > s->length ? s->length - s->rep : s->repLen) : 0u; instAdd(m, s, TYPE_PLAIN); inst = &m->insts[m->instCount - 1u]; if (s->streamed) { if (s->looped) { for (k = 0; k < s->rep; k++) { toolBufByte(&inst->bytes, docByte(s, k)); } addLoop(&inst->bytes, s, s->rep, loopLen, 0u); // The loop's start again, for the buffer's wrap. for (k = 0; k < STREAM_LOOP_COPY; k++) { toolBufByte(&inst->bytes, inst->bytes.data[s->rep + k]); } inst->type = (uint8_t)(TYPE_STREAMED | TYPE_LOOPED | (isIdeal(inst->bytes.len) ? TYPE_IDEAL : 0u)); } else { for (k = 0; k < s->length; k++) { toolBufByte(&inst->bytes, docByte(s, k)); } toolBufZeros(&inst->bytes, STOPPER_BYTES); inst->type = TYPE_STREAMED; } } else if (!s->looped) { for (k = 0; k < s->length; k++) { toolBufByte(&inst->bytes, docByte(s, k)); } finish(inst, TYPE_PLAIN); } else if (s->rep < HEADER_START_MIN) { addLoop(&inst->bytes, s, s->rep, loopLen, 0u); finish(inst, TYPE_LOOPED); } else { split = splitAt(s, s->rep, loopLen); for (k = 0; k < split; k++) { toolBufByte(&inst->bytes, docByte(s, k)); } finish(inst, TYPE_HEADER); instAdd(m, s, TYPE_LOOP); inst = &m->insts[m->instCount - 1u]; addLoop(&inst->bytes, s, s->rep, loopLen, split - s->rep); finish(inst, TYPE_LOOP); } } } // A length a table is exactly: a power of two from TABLE_MIN on. static bool isIdeal(size_t len) { return len >= TABLE_MIN && isPowerOfTwo((uint32_t)len); } static bool isPowerOfTwo(uint32_t v) { return v != 0u && (v & (v - 1u)) == 0u; } // How many times a loop is laid down: a streamed one until it fills a // stream buffer, any other short one enough to play long enough (to // LOOP_IDEAL when that is a whole number of it). static uint32_t loopRepeats(uint32_t loopLen, bool streamed) { if (streamed) { return (STREAM_BUFFER + loopLen - 1u) / loopLen; } if (loopLen >= LOOP_REPEAT_UNDER) { return 1u; } return isPowerOfTwo(loopLen) ? LOOP_IDEAL / loopLen : LOOP_FILL / loopLen; } // The module at path; a fatal error when it is not one. static void modRead(ModT *m, const char *path) { uint8_t *file; const uint8_t *magic; const uint8_t *h; const uint8_t *cell; size_t at; uint32_t i; uint32_t smp; SampleT *s; memset(m, 0, sizeof(*m)); if (!toolReadFile(path, &file, &m->len)) { toolFail("cannot read %s", path); } m->file = file; if (m->len < MOD_HEADER_BYTES) { toolFail("%s is too short for a module", path); } magic = file + MOD_MAGIC_AT; if (memcmp(magic, "M.K.", MOD_MAGIC_BYTES) == 0 || memcmp(magic, "M!K!", MOD_MAGIC_BYTES) == 0 || memcmp(magic, "FLT4", MOD_MAGIC_BYTES) == 0) { m->tracks = 4u; } else if (memcmp(magic, "6CHN", MOD_MAGIC_BYTES) == 0) { m->tracks = 6u; } else if (memcmp(magic, "8CHN", MOD_MAGIC_BYTES) == 0 || memcmp(magic, "FLT8", MOD_MAGIC_BYTES) == 0 || memcmp(magic, "CD81", MOD_MAGIC_BYTES) == 0 || memcmp(magic, "OKTA", MOD_MAGIC_BYTES) == 0 || memcmp(magic, "OCTA", MOD_MAGIC_BYTES) == 0) { m->tracks = 8u; } else { toolFail("%s does not look like a module (no signature at %u)", path, MOD_MAGIC_AT); } memcpy(m->name, file, MOD_NAME_BYTES); m->orders = file[MOD_ORDER_COUNT_AT] < MOD_ORDER_MAX ? file[MOD_ORDER_COUNT_AT] : MOD_ORDER_MAX; m->orderTable = file + MOD_ORDER_AT; for (i = 0; i < MOD_ORDER_MAX; i++) { if (m->orderTable[i] + 1u > m->patterns) { m->patterns = m->orderTable[i] + 1u; } } if (m->orders == 0u) { toolFail("%s has an empty order list", path); } at = MOD_HEADER_BYTES + (size_t)m->patterns * MOD_ROWS * m->tracks * MOD_CELL_BYTES; m->cells = file + MOD_HEADER_BYTES; if (at > m->len) { toolFail("%s ends inside its patterns", path); } for (i = 0; i < MOD_SAMPLES; i++) { s = &m->samples[i]; h = file + MOD_SAMPLE_AT + i * MOD_SAMPLE_BYTES; memcpy(s->name, h, MOD_SAMPLE_NAME); s->length = (((uint32_t)h[MOD_LENGTH_AT] << BYTE_BITS) | h[MOD_LENGTH_AT + 1u]) * 2u; s->fine = h[MOD_FINE_AT] & FINE_MASK; s->volume = h[MOD_VOLUME_AT]; s->rep = (((uint32_t)h[MOD_REP_AT] << BYTE_BITS) | h[MOD_REP_AT + 1u]) * 2u; s->repLen = (((uint32_t)h[MOD_REPLEN_AT] << BYTE_BITS) | h[MOD_REPLEN_AT + 1u]) * 2u; s->data = file + (at < m->len ? at : m->len); s->have = at < m->len ? (uint32_t)(m->len - at < s->length ? m->len - at : s->length) : 0u; s->looped = s->repLen > LOOP_MIN; if (s->looped && s->rep >= s->length && s->length > 0u) { fprintf(stderr, "%s: sample %u's loop starts at or past its end; it plays unlooped\n", TOOL_NAME, i + 1u); s->looped = false; } s->streamed = s->length > STREAM_MAX; at += s->length; } // A sample played with a sample offset (effect 9, a nonzero offset) // streams: the replayer offsets only streamed samples. for (cell = m->cells; cell < m->cells + (size_t)m->patterns * MOD_ROWS * m->tracks * MOD_CELL_BYTES; cell += MOD_CELL_BYTES) { smp = (cell[0] & SAMPLE_HIGH_MASK) | (uint32_t)(cell[2] >> NIBBLE_BITS); if (smp != 0u && (cell[2] & EFFECT_MASK) == EFFECT_OFFSET && cell[3] != 0u) { m->samples[smp - 1u].streamed = true; } } } // The sample needs two oscillators: streamed, a header and a loop, or a // loop that is not ideal. static bool needsTwo(const ModT *m, const SampleT *s) { const InstT *inst; if (s->streamed) { return true; } inst = &m->insts[s->first - 1u]; return (inst->type & TYPE_LOOP) == TYPE_HEADER || (inst->type & (TYPE_IDEAL | TYPE_LOOP)) == TYPE_LOOPED; } // A period as NTP's note number; 0 for none. static uint32_t noteOf(uint32_t period) { uint32_t n; if (period == 0u) { return 0u; } for (n = 0; n < NOTES - 1u && period < kNoteFloor[n]; n++) { } return n + 1u; } // The NTP file. static void ntpWrite(const ModT *m, const char *path) { const uint8_t *cell; const InstT *inst; ToolBufT out; uint32_t i; uint32_t p; uint32_t r; uint32_t t; uint32_t osc; uint32_t step; uint32_t smp; uint32_t code; uint32_t period; bool oscUsed[OSCS_MAX]; memset(&out, 0, sizeof(out)); toolBufAdd(&out, NTP_MAGIC, NTP_MAGIC_BYTES); toolBufByte(&out, NTP_VERSION); toolBufByte(&out, m->tracks); toolBufByte(&out, m->instCount); toolBufByte(&out, m->patterns); toolBufByte(&out, m->orders); toolBufByte(&out, (uint32_t)strlen(m->name)); toolBufAdd(&out, m->name, strlen(m->name)); memset(oscUsed, 0, sizeof(oscUsed)); for (t = 0; t < m->tracks; t++) { toolBufByte(&out, (t % 4u == 0u || t % 4u == 3u) ? CHANNEL_RIGHT : CHANNEL_LEFT); toolBufByte(&out, m->buffer[t] / PAGE_BYTES); // The lowest free oscillator, or the lowest free DOC pair (even // then odd). step = m->twoOsc[t] ? 2u : 1u; for (osc = 0; oscUsed[osc] || oscUsed[osc + step - 1u]; osc += step) { } oscUsed[osc] = true; oscUsed[osc + step - 1u] = true; toolBufByte(&out, (m->twoOsc[t] ? OSC_TWO : 0u) | osc); } for (i = 0; i < m->instCount; i++) { inst = &m->insts[i]; toolBufByte(&out, inst->type); toolBufLe16(&out, (uint32_t)inst->bytes.len); toolBufByte(&out, (uint32_t)(inst->bytes.len >> 16)); toolBufLe16(&out, inst->sample->rep); toolBufByte(&out, inst->sample->rep >> 16); toolBufLe16(&out, inst->sample->repLen); toolBufByte(&out, inst->sample->repLen >> 16); toolBufByte(&out, inst->sample->volume); toolBufByte(&out, inst->sample->fine); if ((inst->type & TYPE_STREAMED) != 0u) { toolBufByte(&out, 0u); toolBufByte(&out, 0u); } else { for (code = 0; (TABLE_MIN << code) < inst->table; code++) { } toolBufByte(&out, inst->address / PAGE_BYTES); toolBufByte(&out, code * WAVE_SIZE_SCALE); } toolBufByte(&out, (uint32_t)strlen(inst->sample->name)); toolBufAdd(&out, inst->sample->name, strlen(inst->sample->name)); } toolBufAdd(&out, m->orderTable, m->orders); for (p = 0; p < m->patterns; p++) { for (r = 0; r < MOD_ROWS; r++) { for (t = 0; t < m->tracks; t++) { cell = m->cells + (((size_t)p * MOD_ROWS + r) * m->tracks + t) * MOD_CELL_BYTES; smp = (cell[0] & SAMPLE_HIGH_MASK) | (uint32_t)(cell[2] >> NIBBLE_BITS); period = ((uint32_t)(cell[0] & PERIOD_HIGH_MASK) << BYTE_BITS) | cell[1]; toolBufByte(&out, noteOf(period)); toolBufByte(&out, smp == 0u ? 0u : m->samples[smp - 1u].first); toolBufByte(&out, cell[2] & EFFECT_MASK); toolBufByte(&out, cell[3]); } } } for (i = 0; i < m->instCount; i++) { toolBufAdd(&out, m->insts[i].bytes.data, m->insts[i].bytes.len); } if (!toolWriteFile(path, out.data, out.len)) { toolFail("cannot write %s", path); } toolBufFree(&out); } static void orderSwap(uint32_t *o, uint32_t a, uint32_t b) { uint32_t held; held = o[a]; o[a] = o[b]; o[b] = held; } // The DOC pages bytes take. static uint32_t pagesOf(uint32_t bytes) { return (bytes + PAGE_BYTES - 1u) / PAGE_BYTES; } // One quicksort pass of sortClaims: the claims from position 2 on split // about the pivot at 1; returns the position past those it goes after. static uint32_t partition(const ClaimT *c, uint32_t *o, uint32_t n) { uint32_t i; uint32_t j; i = 2u; j = n - 1u; for (;;) { while (claimAfter(c, o, 1u, i)) { i++; if (i == j) { return i; } } j--; if (j == i) { return i; } while (claimAfter(c, o, j, 1u)) { j--; if (j == i) { return i; } } orderSwap(o, i, j); i++; if (i == j) { return i; } } } // The instruments and then the tracks' stream buffers, as one list, sorted // by sortClaims and each placed at the lowest address its table size // divides where its pages are free. Page 0 is the interrupt timer's when // no instrument fills a page. False when something does not fit. static bool place(ModT *m) { ClaimT claims[ITEMS_MAX]; uint32_t order[ITEMS_MAX]; bool used[DOC_PAGES]; bool given[MOD_TRACKS_MAX]; ClaimT *claim; uint32_t count; uint32_t i; uint32_t j; uint32_t t; uint32_t pages; uint32_t addr; uint32_t p; bool timer; bool fits; count = 0u; timer = true; for (i = 0; i < m->instCount; i++) { if ((m->insts[i].type & TYPE_STREAMED) == 0u) { claims[count++] = (ClaimT){ (uint32_t)m->insts[i].bytes.len, m->insts[i].table, &m->insts[i].address, false }; timer = timer && m->insts[i].bytes.len < PAGE_BYTES; } } // A buffer for each track that plays a streamed sample: sample by // sample, the tracks playing it in the order they first play anything. memset(m->buffer, 0, sizeof(m->buffer)); memset(given, 0, sizeof(given)); for (i = 0; i < MOD_SAMPLES; i++) { if (!m->samples[i].streamed) { continue; } for (j = 0; j < m->tracksSeen; j++) { t = m->trackOrder[j]; if ((m->plays[t] >> i & 1u) != 0u && !given[t]) { given[t] = true; claims[count++] = (ClaimT){ STREAM_BUFFER, STREAM_BUFFER, &m->buffer[t], true }; } } } for (i = 0; i < count; i++) { order[i] = i; } sortClaims(claims, order, count); memset(used, 0, sizeof(used)); used[0] = timer; for (i = 0; i < count; i++) { claim = &claims[order[i]]; pages = pagesOf(claim->size); fits = false; for (addr = 0; addr + claim->table <= DOC_BYTES && !fits; addr += claim->table) { fits = true; for (p = addr / PAGE_BYTES; p < addr / PAGE_BYTES + pages; p++) { fits = fits && !used[p]; } if (fits) { *claim->where = addr; for (p = addr / PAGE_BYTES; p < addr / PAGE_BYTES + pages; p++) { used[p] = true; } } } if (!fits) { return false; } } return true; } // The claim at position last moved down, a place at a time, past those it // goes before. static void sinkLast(const ClaimT *c, uint32_t *o, uint32_t last) { uint32_t k; for (k = last; k > 0u && claimAfter(c, o, k - 1u, k); k--) { orderSwap(o, k - 1u, k); } } // The claims at positions x, y, and z put in order (PHP's way, which // claimAfter's equal buffers and instruments make matter). static void sort3(const ClaimT *c, uint32_t *o, uint32_t x, uint32_t y, uint32_t z) { if (!claimAfter(c, o, x, y)) { if (claimAfter(c, o, y, z)) { orderSwap(o, y, z); if (claimAfter(c, o, x, y)) { orderSwap(o, x, y); } } return; } if (!claimAfter(c, o, z, y)) { orderSwap(o, x, z); return; } orderSwap(o, x, y); if (claimAfter(c, o, y, z)) { orderSwap(o, y, z); } } // The claims in placing order, sorted step for step as PHP 8 sorts // (zend_sort, after libc++'s std::sort), which is what Ninjaforce's // converter's placements come from: over SORT_SMALL claims, the middle one // sorted with the first and last, moved to position 1 as the pivot, the // rest split about it, and the smaller part sorted first; SORT_SMALL or // fewer by sortInsert. (PHP takes its pivot from five claims past 1023, // many more than a module makes.) static void sortClaims(const ClaimT *c, uint32_t *o, uint32_t n) { uint32_t i; while (n > SORT_SMALL) { sort3(c, o, 0u, n / 2u, n - 1u); orderSwap(o, 1u, n / 2u); i = partition(c, o, n); orderSwap(o, 1u, i - 1u); if (i - 1u < n - i) { sortClaims(c, o, i - 1u); o += i; n -= i; } else { sortClaims(c, o + i, n - i); n = i - 1u; } } sortInsert(c, o, n); } // PHP's insertion sort: under SORT_LINEAR claims, the first three by // sort3 and the rest moved down a place at a time; more, the first // SORT_LINEAR moved down a place at a time and the rest found a place for // two at a time. static void sortInsert(const ClaimT *c, uint32_t *o, uint32_t n) { uint32_t i; uint32_t j; uint32_t k; uint32_t held; i = 1u; if (n < SORT_LINEAR && n >= SORT_NETWORK) { sort3(c, o, 0u, 1u, 2u); i = SORT_NETWORK; } for (; i < n && i < SORT_LINEAR; i++) { sinkLast(c, o, i); } for (; i < n; i++) { if (!claimAfter(c, o, i - 1u, i)) { continue; } j = i - 1u; for (;;) { j -= 2u; if (!claimAfter(c, o, j, i)) { j += claimAfter(c, o, j + 1u, i) ? 1u : 2u; break; } if (j == 0u) { break; } if (j == 1u) { j = claimAfter(c, o, i, 0u) ? 1u : 0u; break; } } held = o[i]; for (k = i; k > j; k--) { o[k] = o[k - 1u]; } o[j] = held; } } // The first byte of the loop (loopLen bytes from start) nearest the DOC's // centre: where a header ends and its loop begins. static uint32_t splitAt(const SampleT *s, uint32_t start, uint32_t loopLen) { uint32_t best; uint32_t bestDistance; uint32_t distance; uint32_t i; uint8_t v; best = start; bestDistance = DOC_CENTRE + 1u; for (i = start; i < start + loopLen; i++) { v = docByte(s, i); distance = v >= DOC_CENTRE ? v - DOC_CENTRE : DOC_CENTRE - v; if (distance < bestDistance) { best = i; bestDistance = distance; } } return best; } // Which tracks play an instrument that needs two oscillators, and which // play a streamed one: every cell of every pattern in the file. static void tracksScan(ModT *m) { const uint8_t *cell; uint32_t p; uint32_t r; uint32_t t; uint32_t smp; memset(m->twoOsc, 0, sizeof(m->twoOsc)); memset(m->plays, 0, sizeof(m->plays)); m->tracksSeen = 0u; for (p = 0; p < m->patterns; p++) { for (r = 0; r < MOD_ROWS; r++) { for (t = 0; t < m->tracks; t++) { cell = m->cells + (((size_t)p * MOD_ROWS + r) * m->tracks + t) * MOD_CELL_BYTES; smp = (cell[0] & SAMPLE_HIGH_MASK) | (uint32_t)(cell[2] >> NIBBLE_BITS); if (smp == 0u) { continue; } if (m->samples[smp - 1u].length == 0u) { toolFail("pattern %u, line %u, track %u plays sample %u, which is empty", p, r, t + 1u, smp); } if (m->plays[t] == 0u) { m->trackOrder[m->tracksSeen++] = t; } m->twoOsc[t] = m->twoOsc[t] || needsTwo(m, &m->samples[smp - 1u]); m->plays[t] |= 1u << (smp - 1u); } } } } int main(int argc, char **argv) { ModT *m; SampleT *longest; uint32_t i; toolFileName(TOOL_NAME); if (argc != 3) { fprintf(stderr, "usage: %s IN.MOD OUT.NTP\n", TOOL_NAME); return USAGE_EXIT; } m = (ModT *)calloc(1u, sizeof(ModT)); if (m == NULL) { toolFail("out of memory"); } modRead(m, argv[1]); for (;;) { instruments(m); tracksScan(m); if (place(m)) { break; } // Stream the longest sample not streamed yet (the first of equals). longest = NULL; for (i = 0; i < MOD_SAMPLES; i++) { if (m->samples[i].length > 0u && !m->samples[i].streamed && (longest == NULL || m->samples[i].length > longest->length)) { longest = &m->samples[i]; } } if (longest == NULL) { toolFail("%s does not fit in DOC RAM even streamed", argv[1]); } longest->streamed = true; } ntpWrite(m, argv[2]); return 0; }