joeylib2/tools/modToNtp/modToNtp.c

906 lines
31 KiB
C

// 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}