Advertisement

writing a s3m file saver for modplug tracker

Started by December 30, 2024 09:37 PM
4 comments, last by MitsubishiMagna 4 days, 10 hours ago

So I've written a tracker, and I'd like to support s3m files so I can be played in other trackers.

Problem is modplug tracker isn't accepting the file I'm writing.

Its a single function, Its got extra stuff in for converting the data across from my system, so it obfuscates the code a bit unfortunately.

The code is a bit messy and has some spare variables, I'm actually rendering the samples with a synthesizer, just to be clear.

Im setting up instruments, rendering the samples, and setting the offsets at the top, and all the disk writing is at the bottom.

I made it from an s3m loader on a bisquit youtube video, and it has some wierd parts to it I dont really like the look of.

If only I could get an actual s3m file writer! that would help me heaps.

Adding my music information to the s3m made the file alot bigger, all the writes are at the bottom where the fopen is.

So here is the function in entirety:

[code = “c++”]


#define MAX_PATLENGTH 10000
#define MAX_SAMPLELENGTH 1000000
char* data_patterns = new char[128 * MAX_PATLENGTH]; //the pattern information is in bytes
char* data_samples = new char[128 * MAX_SAMPLELENGTH]; //the sample information in bytes.

struct s3mheader
{
char name[28]; // song name
unsigned char eofchar, typ, dummy[2];
unsigned short ordnum, insnum, patnum, flags, cwtv, ffi;
char scrm[4];
unsigned char Vxx, Axx, Txx, mastervolume, uc, dp, dummy2[8];
unsigned short special;
unsigned char channelsettings[32];
// The rest of this struct is filled on demand
unsigned char orders[256], * patdata[100], * patend[100];
};

struct s3minst
{
unsigned char type;
char filename[12];
unsigned char filepos[3];
long length, loopbegin, loopend;
unsigned char volume, dummy, packflag, flags;
unsigned long c4spd;
unsigned char* sampledata; // internal, used in runtime
double c4spd_factor; // internal, used in runtime
unsigned char name[28], scri[4];
};

void save_s3m2(const char* filename, int voices)
{
s3m_header s3h;
s3mheader name;
s3minst ins[100];
unsigned short inspointers[99];
unsigned short patpointers[100];
unsigned long samplepointers[100];

uint i;
uint j, k;

uint length1[101];

//setup instruments.
for (i = 0; i < voices; i++)
{
memset(&ins[i], 0, sizeof(s3minst));

//render the wave file here
tvoice[0] = cur_voice;
tnote[0] = DN1;
tstop[0] = false;
tstop2[0] = false;
uint j;
for (j = 0; j < mod1[0].voice[i].end - mod1[0].voice[i].start; j++)
{
data_samples[i * MAX_SAMPLELENGTH + j] = render_synth(0, 1, j, false);
}

strcpy_m(ins[i].filename, mod1[0].voice[i].name, strlen(mod1[0].voice[i].name));
ins[i].type = 0;
ins[i].scri[0] = 'S';
ins[i].scri[1] = 'C';
ins[i].scri[2] = 'R';
ins[i].scri[3] = 'S';
ins[i].length = mod1[0].voice[i].end - mod1[0].voice[i].start; //the wave file size
ins[i].flags = 1 << 6;
ins[i].name[0] = 0;
ins[i].loopbegin = 0;
ins[i].loopend = ins[i].length;
ins[i].volume = 0xFF;
ins[i].c4spd = 168;
ins[i].c4spd_factor = 229079296.0 / ins[i].c4spd;
}

int off, offd16, offm16;

//set offsets.
for (i = 0; i < voices; i++)
{
if (i == 0)
{
off = 0x60 + mod1[0].patts + voices * 2 + mod1[0].patts * 2;
offd16 = off / 16;
offm16 = off % 16;
if (offm16 > 0) offd16++;
inspointers[i] = offd16;
}
else
{
off = inspointers[i-1]+sizeof(s3minst);
offd16 = off / 16;
offm16 = off % 16;
if (offm16 > 0) offd16++;

inspointers[i] = offd16;
}
}

//pattern offsets
for (i = 0; i < mod1[0].patts; i++)
{
//set pattern data
memset(&data_patterns[i * MAX_PATLENGTH], 0, MAX_PATLENGTH);

length1[i] = 0;
for (k = 0; k < mod1[0].patt[i].ticks; k++)
{
for (j = 0; j < TRACKS; j++)
{
if (mod1[0].patt[i].track[j].note[k] != NON)
{
unsigned char byte = j;
byte += 1 << 5;
byte += 1 << 6;

data_patterns[i * MAX_PATLENGTH + length1[i]] = byte;
length1[i]++;
data_patterns[i * MAX_PATLENGTH + length1[i]] = mod1[0].patt[i].track[j].note[k];
length1[i]++;
data_patterns[i * MAX_PATLENGTH + length1[i]] = mod1[0].patt[i].track[j].voice[k];
length1[i]++;
data_patterns[i * MAX_PATLENGTH + length1[i]] = mod1[0].patt[i].track[j].amp[k]*64/255;
length1[i]++;
}
else
if (mod1[0].patt[i].track[j].amp[k]>0)
{
unsigned char byte = j;
byte += 1 << 6;
data_patterns[i * MAX_PATLENGTH + length1[i]] = byte;
length1[i]++;
data_patterns[i * MAX_PATLENGTH + length1[i]] = mod1[0].patt[i].track[j].amp[k]*64/255;
length1[i]++;
}
}
data_patterns[i * MAX_PATLENGTH + length1[i]] = 0;
length1[i]++;
}

if (i == 0)
{
off = inspointers[voices - 1] * 16 + sizeof(s3minst);
offd16 = off / 16;
offm16 = off % 16;
if (offm16 > 0) offd16++;
patpointers[i] = offd16;
}
else
{
off = patpointers[i - 1] * 16 + length1[i - 1];
offd16 = off / 16;
offm16 = off % 16;
if (offm16 > 0) offd16++;
patpointers[i] = offd16;
}

}

//setup sample offsets
for (i = 0; i < voices; i++)
{
if (i == 0)
{
off = patpointers[mod1[0].patts - 1] * 16 + length1[mod1[0].patts - 1];
samplepointers[i] = off;

unsigned long sp = samplepointers[i];
unsigned long big = sp / 0x100000UL/2;
ins[i].filepos[0] = big;
sp -= big *0x100000UL * 2;
int medium = sp / 0x1000UL;
ins[i].filepos[2] = medium;
sp -= medium;
int sps = sp % 0x10UL;
sp /= 0x10UL;
if (sps > 1)
{
sp++;
}
ins[i].filepos[1] = sp;
}
else
{
off = samplepointers[i - 1] + ins[i - 1].length;
samplepointers[i] = off;

unsigned long sp = samplepointers[i];
unsigned long big = sp / 0x100000UL / 2;
ins[i].filepos[0] = big;
sp -= big * 0x100000UL * 2;
int medium = sp / 0x1000UL;
ins[i].filepos[2] = medium;
sp -= medium;
int sps = sp % 0x10UL;
sp /= 0x10UL;
if (sps > 1)
{
sp++;
}
ins[i].filepos[1] = sp;

}
}


strcpy_m(name.name, "Scream", strlen("Scream"));

char orders[256];
for (i = 0; i < mod1[0].patts; i++) { orders[i] = i; }

memset(&name, 0, sizeof(s3mheader));

name.ordnum = mod1[0].patts; //<256
name.insnum = voices; //<=99
name.patnum = mod1[0].patts; //<= 100

name.eofchar = 0x1A;
name.typ = 0x10;


name.scrm[0] = 'S';
name.scrm[1] = 'C';
name.scrm[2] = 'R';
name.scrm[3] = 'M';


FILE* fp;
fopen_s(&fp, filename, "wb+");

fwrite(&name, 1, 0x60, fp);
fwrite(&orders, 1, mod1[0].patts, fp);
fwrite(&inspointers, 2, voices, fp);
fwrite(&patpointers, 2, mod1[0].patts, fp);
for (i = 0; i < voices; ++i)
{
fseek(fp, inspointers[i] * 16UL, SEEK_SET);
fwrite(&ins[i], 1, 0x50, fp);
unsigned long smppos = ins[i].filepos[1] * 0x10UL
+ ins[i].filepos[2] * 0x1000UL
+ ins[i].filepos[0] * 0x100000UL + ins[i].filepos[0] * 0x100000UL;
fseek(fp, smppos, SEEK_SET);
fwrite(&data_samples[i*MAX_SAMPLELENGTH], 1, ins[i].length, fp);
}

for (unsigned p = 0; p < mod1[0].patts; ++p)
{
unsigned short length = 2;
fseek(fp, patpointers[p] * 16UL, SEEK_SET);
fwrite(&length1[p], 1, 2, fp);
fwrite(&data_patterns[p*MAX_PATLENGTH], 1, length - 2, fp);
}
fclose(fp);

}

[/code]

I've got no idea what I have got wrong, So apart from looking at my function, which might be a bit bothersome I don't have an idea of what I can do in the future, I'm pretty much stuck.

All I have is access to loaders, not savers, so I might be missing something in the save, that the load hasnt got in it.

Can someone help?

Im looking here for help→ https://moddingwiki.shikadi.net/wiki/S3M_Format

Is there anywhere else I can go?

Advertisement

I worked out what I needed to do.

The thing I needed to do was load an existing s3m and then save it back to another test file, and I got it running, so now I'm going to hack my patterns and voices into it.

See how I go…

That's a blast from the past. Mods and scream tracker files were amazing back in the 90s when sound cards were getting common but connections were double digit kbps dial up. MIDI worked if attached to a quality synth keyboard and they were written to use the instrument, but most weren't so sampling was the best.

Glad to see you got it figured out so quickly before people could reply. Discord is more common if you are looking for a fast discussion, the forum for slowly moving back and forth.

Yeh I got it working!

I just needed the modplug tracker to accept it then it was just trial and error after that, I managed to hack my instruments and patterns into it successfully, but without it even loading in at all I was stuffed.

My first s3m u can test it out if you want, https://forum.openmpt.org/index.php?topic=7272.0

I'm doing a game with it, but I'm synthesizing all my own sounds but my synth works with a resampler to change frequencies, it helps the sound stay solid without changing tamber during note changes, they also bake to wav files so it exports as an s3m fine. I'm going to be doing a live synthesis during the game for all original sfx, just like the cool old arcade games did, and the game consoles.

Having fun, thanks alot. and antialiasing sucks. 🙂

Advertisement