@MIST,
@Mu0n . I've been modifying the code a bit to free it from the built-in .mod file. I found that the assembly code in prepare probably isn't correct for the Mac.
Here's what happens. After Init, all the waveform pointers are stored in
sample starts: DS.L 31 and the waveforms themselves start at the beginning of the waveform data in the .mod file and end at the calculated address from
init: , stored in
end_of_samples: DC.L . So, the mod_ptr memory block looks like this:
+---------+----+--------+---------+---------+
|ModHeader|Song|Patterns|Waveforms|Workspace|
+---------+----+--------+---------+---------^workspc
What prepare does is shift all the waveforms to the end of the memory block, initially leaving the equivalent workspace gap just after patterns.
+---------+----+--------+-------------------+
|ModHeader|Song|Patterns|Garbage...Waveforms|
+---------+----+--------+-------------------^workspc
Then it goes through all the Waveforms 0..31; copying each one back from the start of the original waveform area but
expanding each waveform by its repeated sample section - what's called the Sample loop in synthesiser terminology. Since the waveform envelope of a waveform is always:
+---------(46)---------+----------------+
|Unlooped |LoopSection |NoNoteOffSection|
<--(42)--><----(48)---->----------------+
Then duplicating the loop section at the end of a waveform means that DMA can play off the end of a loop section for all the samples in a given tick and it'll work as if it had looped round.
There are a few bugs in the code that means it's quite possible for the expanded Waveforms to overflow the workspace and I guess that's what's causing the crashes when we allocate data for a mod file; whereas when the mod file is embedded in the CODE segment, if the workspace overflows, it's just overflowing into the unused heap space between the end of CODE and the return stack.
Whatever code changes we make, the Mac NanoTrack player needs to do pretty much the same thing, because the waveform pointer can only jump back at the end of the
sample: function. It mustn't go off the end of a LoopSection into the next waveform. But because even an early Mac has well managed heaps; we shouldn't just guess the workspace needed; we should calculate it properly. We should be able to do that in 'C', because it's not timing-critical.
Also, the proper way to do it is to read in the mod header (which is quite short). We can allocate that to a handle and move it hi. We parse the header to find out how much actual waveform space is needed + the repeated looping sections. Then do a NewPtrClear for all of that; read in each waveform in turn and fill in the correct
SampleStart, which is what
init does.
How much duplicated LoopSection is really needed? If we look at the table from
this spec URL we get these periods:
C C# D D# E F F# G G# A A# B
Octave 1: 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453
Octave 2: 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226
Octave 3: 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113
Octave 0:1712,1616,1525,1440,1357,1281,1209,1141,1077,1017, 961, 907
Octave 4: 107, 101, 95, 90, 85, 80, 76, 71, 67, 64, 60, 57
Amiga sample rates are PAL = 7093789.2/
(2*periods) NTSC=7159090.5/(2*
periods).
On the Mac, increment rates are: INC=(3579546/(60*LEN)*65536) = 161*65536 =10,551,296 or 0xA10000. So, for a start, it's not correct, because it should be: 10,567,078 or 0xA13DA6. Anyway, the lowest frequency in the frequency table is 0x30, which means that the sample increment for that is: 3.354171753.
So, at most we can have an extra 370x3.354171753=1,241 samples read at the end of a waveform. This needs to be rounded up to an even number so, in turn that means the maximum workspace is around: 1,242*31=38,502 bytes.
I think a suitable replacement for the assembly
prepare function will look like this:
C:
#define kModWaveNameLen (22)
#define kModWaveFormsMax (31)
#define kWaveExpansion (1242)
typedef struct {
char iName[kModWaveNameLen]; //22
uint16_t iWordLen; //2
uint8_t iFineTune; //1
uint8_t iVolume; //1
uint16_t iLoopStartWords; //2
uint16_t iLoopLenWords; // 2
}tModWaveHeader; // 30 bytes.
void ModWaveExpand(tModWaveHeader aWaveInfo[], uint8_t *aWaveStarts[], uint8_t *aWavesEnd)
{
int16_t wave=kModWaveFormsMax; // Start at last waveform.
uint32_t len,loopLen;
uint8_t *loopFrom;
while(wave>=0 && aWaveStarts[wave]==NULL) {
--wave;
} // find last wave.
for(; wave>=0; wave--) { // searching backwards.
len=ModWordsToBytes(aWaveInfo[wave].iWordLen);
BlockMove(aWaveStarts[wave],aWavesEnd-len-kWaveExpansion,len);
aWaveStarts[wave]=aWavesEnd-len-kWaveExpansion; // update waveStarts.
aWavesEnd=aWaveStarts[wave]+len;
loopLen=aWaveInfo[wave].iLoopLenWords;
if(loopLen!=0) { // fill expansion with repeats of loop.
len=kWaveExpansion; // bytes to fill.
loopFrom=aWaveStarts[wave]+ModWordsToBytes(aWaveInfo[wave].
iLoopStartWords);
while(len) {
if(len<loopLen) { // just copy the remaining len
loopLen=len; // bytes on last pass.
}
BlockMove(loopFrom,aWavesEnd,loopLen);
aWavesEnd+=loopLen;
len-=loopLen;
}
}
else { //Non-loop case:
memset(aWavesEnd, 0, kWaveExpansion); // clear expansion area
}
aWavesEnd=aWaveStarts[wave];
}
}
So, I intend to eliminate the init and prepare assembly functions and use this and my replacement for init.