/* (tabstops=8)
°°°°°°°°°°°°°°±±±±±±±±±±±±±²²²²²²²²²²²²²²²²²²²²²²²²±±±±±±±±±±±±±°°°°°°°°°°°°°°
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ °±² : SECTION 0: ²±° ³
³ °±² Index ²±° ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ °±² : SECTION 1: ²±° ³
³ °±² Introduction ²±° ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
- Section 3.4, Inside Update row rewritten, the old one was weird and crap
- Pattern break and pattern jump more accurately described
- New section 2.6.1 - Four bytes?
- Section 3.5.1 rewritten.
Now is a GOOD time to do some thorough testing. Do these things
- Make sure your sample headers and information are stored correctly
- Make sure your pattern data is stored perfectly.. it's quite important you
know :)
- Make sure your samples are stable in memory, and try to play them through
your sound card.. you can have a few problems with misloaded samples I have
found :) Also make sure the loop points are played correctly!
- Make sure you deallocate your memory before quitting the program!!
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ °±² : SECTION 3 : ²±° ³
³ °±² Playing the MOD ²±° ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ °±² 3.1 OK where do I start ²±° ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
I think the main thing you need to do now once you are satisfied your MOD is
loaded properly, is to set up an interrupt function, and understand a bit
about the way a MOD is played.
Im going to use the system timer to hook onto here as an example, and if you
want to use other interrupt servicers you can do that if you know how..
(ie GUS IRQ).
You should know how to set up an interrupt handler yourself, but ill describe
how to do it here with a bit of code to demonstrate.
The system timer lies on INT 8
- Get the old handlers vector for int 8h, and store it away for later
- Set your new handler function to the vector for int 8h
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ REMEMBER TO REHOOK YOUR OLD TIMER TO ITS ORIGNAL PLACE WHEN THE SONG IS ³
³ FINISHED! ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
In C you would do that like this:
oldhandler = _dos_getvect(8);
setvect(8, handler);
- where oldhandler has to have the prototype globally declared as
void interrupt ( *oldhandler)(...);
- for dummies the actuall handler function looks like this
void interrupt modhandler(...) { // yes put 3 dots in here
... // do main loop here
oldmodhandler(); // this is here to return int8 to what it
// normally did. I'll crash without it.
}
In PASCAL it would look something like this
GetIntVec($8, Addr(OldTimer));
SetIntVec($8, Addr(ModInterrupt));
with the function looking something like (I have no idea if this is right
as I don't do pascal)
{ $ F+,S-,W-}
Procedure modhandler; Interrupt;
Begin
...
OldTimer;
End;
{ $ F-,S+}
If you're still not sure in C or pascal, check out the online manual on
getvect/setvect etc..
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ °±² 3.2 Setting the timer's speed ²±° ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
Ok now your interrupt handler is already firing :) so one thing you must
do is set it to the right speed, we don't want mods that play way to fast or
slow, we want it at 125 BPM right now (or 50hz, or 50 ticks a second).
How do you set the system timer's speed? if we want 50hz, we have to use
a divisor to calculate the right rate like so.
Speed = 1193180/50 ← 50 hz here, 1193180 is the divisor.
mov dx, 0x43
mov al, 0x36
out dx, al
mov dx, 0x40
mov ax, Speed <- here's the speed variable
out dx, al
shr ax, 8
out dx, al
Now the interrupt function should be ticking away at 50 times a second.
For other BPM's, which will be used because of the change tempo effect Fxy
with values of 20h and up. If it is below 20h, then you change the SPEED and
not the BPM. This is looked at later on.
To convert BPM to HZ, you use :
HZ = 2 * BPM / 5 (i.e 125bpm = 50hz)
then SPEED = 1193180 / HZ for the set timer routine.
Simple huh. You'll need this for effect Fxy, but don't worry about this until
later.
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ °±² 3.3 Player Logic ²±° ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
Now lets take a look at the interrupt function, this is where the playing
is done.
The SPEED of a song is the base on how your mod is played. Each row of a
pattern is updated every SPEED number of clock ticks, so if a speed is 6,
then you only update each row every 6 clock ticks. So on a speed like 3,
the row is going to be updated every 3 ticks and will play twice as fast as
speed 6.
Inbetween you update certain tick sensitive effects, like portamentos,
volume slides and vibrato.
Diagramatically the playing of a mod looks like this.
SPEED IS 6
tick#
ÚÄÙ
0: UPDATE ROW #0 ← update the 4,6 or 8 notes here in a mod's row.
1: — \
2: — \
3: — >- certain effects are updated here
4: — /
5: — /
0: UPDATE ROW #1
1: —
2: —
3: —
4: —
5: —
0: UPDATE ROW #2
etc..
Logically a very basic representation of playing a mod looks like this:
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ STATIC TICK = SPEED ³ - declaration, start it off at SPEED, not 0, as we
³ ³ want straight into the 'if tick >= speed condition'
³ TICK = TICK + 1 ³ - now increment the tick counter
³ if TICK >= SPEED ³ - if the tick # is bigger or equal than SPEED then
³ update_row ³ - update the CHANNEL number of notes for the new row
³ tick =0 ³ - reset tick to 0
³ ROW = ROW + 1 ³ - incrememnt our row
³ else update_effect ³ - else we update the tick based effects.
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
But you will have to take into account there are only 64 rows in a pattern,
and if you hit 64 then jump to the next pattern and start at row 0 again.
I say 64 because row 63's effects have to be played out before you jump to
the next pattern.
don't bother with update_effect for some time until you have got update_row
going ok.
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ °±² 3.3.1 Orders/Patterns ²±° ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
Just a short note on this.
When you reach the end of the pattern or whatever, you need to go to the next
order. Now say you had your order pattern numbers stored in an array as they
should be, then it is simply a task of referencing that pattern number
according to the index ORDER, and then repositioning your pattern pointer
accordingly.
ie. If your order list is something like this.
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
Order ³ 0 1 2 3 4 5 6 7 8 9 .... ³
³ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄij
Pattern ³ 0 0 1 4 5 2 3 4 4 6 …. ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
and you have an array of patterns set up as ORDER_TABLE[128].
Selecting the appropriate pattern is as simple as finding ORDER_TABLE[ORDER].
To find the offset in your buffer you should know how to do by now by using
some sort of formula like:
offset = (CHANNELS * 4 * 64 * ORDER_TABLE[ORDER])
bytes, rows
and to find the current row just add (CHANNELS * 4 * row).
so the pattern+row formula ends up as :
offset = (CHANNELS * 4 * 64 * ORDER_TABLE[ORDER]) + (CHANNELS * 4 * row).
I calculate this figure before processing every row and set the pattern
pointer, so that all I have to do is increment the row number or the order
number and this formula will pick it up for me and set the pointer
accordingly.
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ °±² 3.4 Inside update row ²±° ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
Ok on every tick 0, we want to update CHANNELS number of channels
PSEUDOCODE:
- Point your note pointer to the correct offset in the pattern buffer,
according to order and row
Loop CHANNEL number of times {
get NOTE from buffer
get SAMPLE from buffer
get EFFECT from buffer
get EFFECT_PARAMETER from buffer
if (SAMPLE > 0) then {
LAST_INSTRUMENT[CHANNEL] = SAMPLE (we store this for later)
volume[CHANNEL] = default volume of sample#
SetVolume(volume[CHANNEL]) (actually do the hardware set here)
}
if (period >= 0) then {
if (EFFECT does not = 3 and EFFECT does not = 5) then
frequency[CHANNEL] =
FREQ_TAB[NOTE + LAST_INSTRUMENT[CHANNEL]'s finetune]
}
(freq_tab[] should be your amiga frequency lookup table - see sec 3.5)
(this line here is a bit of optimization for your player)
if (effect# = 0 and parameter# = 0) then jump to SKIP_EFFECTS label
—-
—-
PROCESS THE NON TICK BASED EFFECTS (see section 5 how to do this)
ALSO GRAB PARAMETERS FOR TICK BASED EFFECTS (like porta, vibrato etc)
-----
-----
label SKIP_EFFECTS:
if (freqency[CHANNEL] > 0) then SetFrequency(frequency[CHANNEL])
if (period > 0 OR sample_offset > 00FFh) then {
(Why 00FFh? because with sample offset anything below
1 * 100h is considered 0. See section 5.10 about this)
if (vibratowavecontrol = retrig waveform) then {
vibrato_position[CHANNEL] = 0 (see section 5.5 about this)
vibrato_negative[CHANNEL] = 0 (see section 5.5 about this)
}
if (tremolowavecontrol = retrig waveform) then {
tremolo_position[CHANNEL] = 0 (see section 5.8 about this)
tremolo_negative[CHANNEL] = 0 (see section 5.8 about this)
}
PLAYVOICE
* (here is gus biased, I guess for SB mixing you would mix in a
section of the sample into a small buffer and dma it out here.
You also have to take note if the sample is looping or not.. GUS
does this for you of course ;) )
* (also remember to add the sample_offset value to the start of the
sample begin address. If there was no sample offset then this
value would be 0 and it would not affect the outcome.)
}
move pointer to next note in row (ie increment 4 bytes)
}
This is your main inner loop and the part that needs to be optimized. So
make sure you can try and get it as fast as possible.
*NOTE - setfrequency in this example is being passed amiga values, and should
convert it to a relevant hardware value.
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ °±² 3.5 Period Frequencies and Fine Tune ²±° ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
The formula for converting amiga period value to hz, is accomplished using
ONE of the following formulas. Why there are 2 will be explained shortly.
You are going to have to convert amiga frequencies to some sort of speed or
frequency factor for YOUR sound card, so this part will show you how.
PAL: 7093789.2 / (amigaval * 2)
NSTC: 7159090.5 / (amigaval * 2)
Say if we wanted to find the value in hz for middle note C-2. Looking up
the amiga table we see the value for C-2 is 428 (see table below).
therefore:
PAL: 7093789.2 / (428 * 2) = 8287.14hz
NSTC: 7159090.5 / (428 * 2) = 8363.42hz
A quick explanation on PAL and NSTC. The amiga used to time its mods by
sitting their interrupt handlers on the vertical retrace of the video screen
so the period values they used in the tables are the amount of data to send
to the amiga sound chip between interrupts, therefore changing the speed of
data sent and the pitch of the note. Pretty stupid system huh. But I suppose
back then they just wanted it to work and werent too worried about the future.
Trackers like FastTracker 2 are taking a step in the right direction by
using linear frequency tables.. ST3 took a step backwards by trying to base
s3m on the mod format. This is MUSIC we are talking about not computer
hardware.
Which should I use? you are asking. Well I think the NSTC is the most widely
accepted and used value, but it does not really matter. The only difference
you might hear is a SLIGHT change in pitch, like about one fine tune out
say. Inertia Play has a switch that lets you choose one or the other. Try
flicking between the 2 while a song is playing to see what it is like.
Here is a period table. This is straight out of protracker so it is bugfree,
other tables you might see in like gusplay by cascada have bugs in it. Don't
use it unless you can fix it. (ie the bug is about F-2 with finetune -3 or
so.. FastTracker 1 has the bug try it out.)
mt_PeriodTable
; Tuning 0, Normal
dc.w 856,808,762,720,678,640,604,570,538,508,480,453 ; C-1 to B-1
dc.w 428,404,381,360,339,320,302,285,269,254,240,226 ; C-2 to B-2
dc.w 214,202,190,180,170,160,151,143,135,127,120,113 ; C-3 to B-3
; Tuning 1
dc.w 850,802,757,715,674,637,601,567,535,505,477,450 ; same as above
dc.w 425,401,379,357,337,318,300,284,268,253,239,225 ; but with
dc.w 213,201,189,179,169,159,150,142,134,126,119,113 ; finetune +1
; Tuning 2
dc.w 844,796,752,709,670,632,597,563,532,502,474,447 ; etc,
dc.w 422,398,376,355,335,316,298,282,266,251,237,224 ; finetune +2
dc.w 211,199,188,177,167,158,149,141,133,125,118,112
; Tuning 3
dc.w 838,791,746,704,665,628,592,559,528,498,470,444
dc.w 419,395,373,352,332,314,296,280,264,249,235,222
dc.w 209,198,187,176,166,157,148,140,132,125,118,111
; Tuning 4
dc.w 832,785,741,699,660,623,588,555,524,495,467,441
dc.w 416,392,370,350,330,312,294,278,262,247,233,220
dc.w 208,196,185,175,165,156,147,139,131,124,117,110
; Tuning 5
dc.w 826,779,736,694,655,619,584,551,520,491,463,437
dc.w 413,390,368,347,328,309,292,276,260,245,232,219
dc.w 206,195,184,174,164,155,146,138,130,123,116,109
; Tuning 6
dc.w 820,774,730,689,651,614,580,547,516,487,460,434
dc.w 410,387,365,345,325,307,290,274,258,244,230,217
dc.w 205,193,183,172,163,154,145,137,129,122,115,109
; Tuning 7
dc.w 814,768,725,684,646,610,575,543,513,484,457,431
dc.w 407,384,363,342,323,305,288,272,256,242,228,216
dc.w 204,192,181,171,161,152,144,136,128,121,114,108
; Tuning -8
dc.w 907,856,808,762,720,678,640,604,570,538,508,480
dc.w 453,428,404,381,360,339,320,302,285,269,254,240
dc.w 226,214,202,190,180,170,160,151,143,135,127,120
; Tuning -7
dc.w 900,850,802,757,715,675,636,601,567,535,505,477
dc.w 450,425,401,379,357,337,318,300,284,268,253,238
dc.w 225,212,200,189,179,169,159,150,142,134,126,119
; Tuning -6
dc.w 894,844,796,752,709,670,632,597,563,532,502,474
dc.w 447,422,398,376,355,335,316,298,282,266,251,237
dc.w 223,211,199,188,177,167,158,149,141,133,125,118
; Tuning -5
dc.w 887,838,791,746,704,665,628,592,559,528,498,470
dc.w 444,419,395,373,352,332,314,296,280,264,249,235
dc.w 222,209,198,187,176,166,157,148,140,132,125,118
; Tuning -4
dc.w 881,832,785,741,699,660,623,588,555,524,494,467
dc.w 441,416,392,370,350,330,312,294,278,262,247,233
dc.w 220,208,196,185,175,165,156,147,139,131,123,117
; Tuning -3
dc.w 875,826,779,736,694,655,619,584,551,520,491,463
dc.w 437,413,390,368,347,328,309,292,276,260,245,232
dc.w 219,206,195,184,174,164,155,146,138,130,123,116
; Tuning -2
dc.w 868,820,774,730,689,651,614,580,547,516,487,460
dc.w 434,410,387,365,345,325,307,290,274,258,244,230
dc.w 217,205,193,183,172,163,154,145,137,129,122,115
; Tuning -1
dc.w 862,814,768,725,684,646,610,575,543,513,484,457
dc.w 431,407,384,363,342,323,305,288,272,256,242,228
dc.w 216,203,192,181,171,161,152,144,136,128,121,114
* I personally used a sorted form of this table, that orders all the notes
from C-1 with -8 finetune, then goes up through all the finetunes to B-3
with finetune +7. Makes things a lot easier I find.
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ °±² 3.5.1 What do I do with this table? ²±° ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
I pondered this one myself for a bit when I first started. It would be nice
if you could just store in the amiga values as your notes, then give them to
your formula to use, and not even use a table to lookup amiga values.
But there lies a problem. Namely finetune and arpeggio. If you have the
amiga values stored as notes, then you will have no idea how much to fine
tune according to the note you are on. If it was a linear table it would be
fine (you would just say 'finetune = 2, so add 2 to the pitch'), but as it
is actually a logarithmic table adding 2 on a C1 note gives a totally
different tone to adding 2 on a C3 note.
Forget storing the actual amiga periods as your notes, in your loader convert
the periods to note numbers (see section 2.6.1), so you can use it to look
up the period table later when the tune is playing.
If you are still a bit confused this is how it is done.
- Loading the pattern data, I looked up the amiga value loaded and gave it
a number from 8 to 288. (36 notes, multiply it by 8 for finetunes between,
remember each note is 8 finetunes apart, so it equals 288.)
- start at 8 (C-1) because there are going to be 8 finetunes below C-1.
- finish at 288 (B-3), and rememer there is going to be 7 finetunes above
it.
- You get this value by reading in the amiga value from the file, and scan
through the period table (given above) until you find a match.
(some trackers don't save the right numbers so I used a check if the number
was between -2 to +2 from the actual value).
Once you find the corresponding value, store the note as your (counter*8)
where counter was the value you were incrementing as you went through the
table.
- Now the pattern data is loaded up with a nice linear set of notes.
- when you actually play it just use your linear value as an index to look
up the amiga table again to get the correct amiga period value.
ok here's how I did it.
period = 1)