Difference between revisions of "Tracker.asm and tracker.mac"
m (Protected "Tracker.asm and tracker.mac" ([edit=autoconfirmed] (indefinite) [move=autoconfirmed] (indefinite))) |
|||
| (One intermediate revision by one other user not shown) | |||
| Line 8: | Line 8: | ||
<CENTER><TABLE BORDER><TR><TH>Macro</TH><TH>Action performed</TH><TH>Notes</TH></TR> | <CENTER><TABLE BORDER><TR><TH>Macro</TH><TH>Action performed</TH><TH>Notes</TH></TR> | ||
<TR><TD>DRUM <I>t_per, n_per, tone, noise, vol</I></TD><TD>Defines drum data for one frame.</TD><TD>A drum is fully defined by 8 frames.</TD></TR> | <TR><TD>DRUM <I>t_per, n_per, tone, noise, vol</I></TD><TD>Defines drum data for one frame.</TD><TD>A drum is fully defined by 8 frames.</TD></TR> | ||
| − | <TR><TD>NOTES <I>n0, n1, n2, n3</I></TD><TD>Defines 4 notes at once</TD><TD>This helps to pattern data in a more compact manner.</TD></TR> | + | <TR><TD>NOTES <I>n0, n1, n2, n3</I></TD><TD>Defines 4 notes at once</TD><TD>This helps to write pattern data in a more compact manner.</TD></TR> |
<TR><TD>NOTE <I>n</I></TD><TD>Defines one note</TD><TD>Expected format for <I>n</I>: "NnO IVL". See comments in source file for details.</TD></TR></TABLE></CENTER> | <TR><TD>NOTE <I>n</I></TD><TD>Defines one note</TD><TD>Expected format for <I>n</I>: "NnO IVL". See comments in source file for details.</TD></TR></TABLE></CENTER> | ||
Latest revision as of 09:38, 4 December 2010
These are two files that work together to provide a "music tracker". tracker.mac provides the macros that allow to write music code as expected by the tracker, in a rather easily readable form. tracker.asm provides the actual tracker functions.
Contents
Functions Provided
tracker.mac
| Macro | Action performed | Notes |
|---|---|---|
| DRUM t_per, n_per, tone, noise, vol | Defines drum data for one frame. | A drum is fully defined by 8 frames. |
| NOTES n0, n1, n2, n3 | Defines 4 notes at once | This helps to write pattern data in a more compact manner. |
| NOTE n | Defines one note | Expected format for n: "NnO IVL". See comments in source file for details. |
tracker.asm
| Entry point | Function provided |
|---|---|
| TRKINIT | Global tracker initialization. |
| TRKSNGINIT | Initialize a new song. |
| TRKSNGINIT.1 | Alternate entry point. |
| TRKPLAY | Ticks the tracker and updates the PSG. |
| TRKPATINIT | (internal function) |
| TRKCHUPD | (internal function) |
| TRKPSGUPD | (internal function) |
Source Code
tracker.mac
;* ======================================================================== *;
;* This code is placed into the public domain by its author. *;
;* All copyright rights are hereby relinquished on the code and data in *;
;* this file. -- Arnauld Chevallier, 2008 *;
;* ======================================================================== *;
;; ======================================================================== ;;
;; DRUM Drum definition ;;
;; ======================================================================== ;;
MACRO DRUM(t_per, n_per, tone, noise, vol)
DECLE %t_per%, %n_per%, $39 - %tone% - 8 * %noise%, %vol%
ENDM
;; ======================================================================== ;;
;; NOTES Defines 4 notes at once ;;
;; ======================================================================== ;;
MACRO NOTES(n0, n1, n2, n3)
;
LISTING "code"
NOTE(%n0%)
NOTE(%n1%)
NOTE(%n2%)
NOTE(%n3%)
LISTING "prev"
ENDM
;; ======================================================================== ;;
;; NOTE Defines one note ;;
;; ======================================================================== ;;
;; Format: ;;
;; "NnO IVL" ;;
;; where: ;;
;; N = Note (C, D, E, F, G, A, B) ;;
;; n = '-' or '#' ;;
;; O = Octave (1 to 7) ;;
;; I = new Instrument (1 to F, or 0 for unchanged) ;;
;; V = Volume (0 to F) ;;
;; L = Length (0 to F) ;;
;; ;;
;; The following strings can be used instead of the NnO parameters: ;;
;; "DRM" --> invokes a drum ;;
;; "NUL" --> no note ;;
;; ;;
;; Examples: ;;
;; "C-3 1F1", "D#4 0F1", "DRM 1F1", "NUL 0F1" ;;
;; ======================================================================== ;;
MACRO NOTE(n)
;
IF STRLEN(%n%) > 0
; instrument
_Hex2Dec(_i, ASC(%n%, 4))
; volume
_Hex2Dec(_v, ASC(%n%, 5))
; length
_Hex2Dec(_l, ASC(%n%, 6))
; "DRM" ?
IF ASC(%n%, 0) = 68 AND ASC(%n%, 1) = 82 AND ASC(%n%, 2) = 77
DECLE (84+_i) * $100 + _l * $10 + (15-_v)
ELSE
; "NUL" ?
IF ASC(%n%, 0) = 78 AND ASC(%n%, 1) = 85 AND ASC(%n%, 2) = 76
IF _i <> 0
DECLE $8000 + _l * $10 + (15-_v)
DECLE (_i - 1) * 3
ELSE
DECLE _l * $10 + (15-_v)
ENDI
ELSE
_InsertNote(%n%, _i, _v, _l)
ENDI
ENDI
ENDI
ENDM
;; ======================================================================== ;;
;; _InsertNote Inserts one note ;;
;; ======================================================================== ;;
;; This was put outside the "Note" macro for readability. ;;
;; ======================================================================== ;;
MACRO _InsertNote(n, i, v, l)
; note
_n0 SET ASC(%n%, 0)
IF (_n0 < 65) OR (_n0 > 71)
ERR "Invalid note"
ENDI
; conversion letter -> value
; ABCDEFG
_n1 SET ASC("JLACEFH", _n0-65) - 64
; sharp ?
IF ASC(%n%, 1) = 35
_n1 SET _n1 + 1
ELSE
; minus ?
IF ASC(%n%, 1) <> 45
ERR "Invalid note"
ENDI
ENDI
; octave
_o SET ASC(%n%, 2)
IF (_o < 49) OR (_o > 55)
ERR "Invalid octave"
ENDI
; value
IF %i% <> 0
DECLE $8000 + (_n1 + 12 * (_o - 49)) * $100 + %l% * $10 + (15-%v%)
DECLE (%i% - 1) * 3
ELSE
DECLE (_n1 + 12 * (_o - 49)) * $100 + %l% * $10 + (15-%v%)
ENDI
ENDM
;; ======================================================================== ;;
;; _Hex2Dec Hexa to decimal conversion ;;
;; ======================================================================== ;;
MACRO _Hex2Dec(var, dec)
;
IF (%dec% >= 48) AND (%dec% <= 57)
%var% SET %dec% - 48
ELSE
IF (%dec% >= 65) AND (%dec% <= 70)
%var% SET %dec% - 55
ELSE
ERR "Invalid hexa value"
ENDI
ENDI
ENDM
;; ======================================================================== ;;
;; End of File: tracker.mac ;;
;; ======================================================================== ;;
tracker.asm
;* ======================================================================== *;
;* These routines are placed into the public domain by their author. All *;
;* copyright rights are hereby relinquished on the routines and data in *;
;* this file. -- Arnauld Chevallier, 2008 *;
;* ======================================================================== *;
;; ======================================================================== ;;
;; TRKINIT Global tracker initialization ;;
;; TRKSNGINIT Song initialization ;;
;; TRKSNGINIT.1 Alternate entry point ;;
;; TRKPLAY Ticks the tracker and updates the PSG ;;
;; ;;
;; TRKPATINIT (internal) Pattern initialization ;;
;; TRKCHUPD (internal) Updates a channel ;;
;; TRKPSGUPD (internal) Updates the PSG ;;
;; ;;
;; AUTHOR ;;
;; Arnauld Chevallier <a_chevallier AT yahoo.com> ;;
;; ;;
;; REVISION HISTORY ;;
;; 07-Sep-2008 Initial Revision ;;
;; ;;
;; INPUTS for TRKINIT ;;
;; none ;;
;; ;;
;; INPUTS for TRKSONGINIT ;;
;; R5 Pointer to invocation record, followed by return address ;;
;; Song base address 1 DECLE ;;
;; ;;
;; INPUTS for TRKSONGINIT.1 ;;
;; R1 Song base address ;;
;; ;;
;; INPUTS for TRKPLAY ;;
;; none ;;
;; ;;
;; CODESIZE ;;
;; 524 words ;;
;; ======================================================================== ;;
;; ======================================================================== ;;
;; VARIABLES IN SCRATCH RAM USED BY THESE ROUTINES ;;
;; ======================================================================== ;;
; TRKSCRACTH ORG $01DC ; (or anywhere else)
; G_FAD RMB 1
; REF_M RMB 1
; NOTE_A RMB 1
; NOTE_B RMB 1
; NOTE_C RMB 1
; REF_A RMB 1
; REF_B RMB 1
; REF_C RMB 1
; VOL_A RMB 1
; VOL_B RMB 1
; VOL_C RMB 1
; INSTR_A RMB 1
; INSTR_B RMB 1
; INSTR_C RMB 1
; COUNT_A RMB 1
; COUNT_B RMB 1
; COUNT_C RMB 1
; COUNT_M RMB 1
; COUNT_P RMB 1
; PAT RMB 1
;; ======================================================================== ;;
;; VARIABLES IN SYSTEM RAM USED BY THESE ROUTINES ;;
;; ======================================================================== ;;
; TRKSYSTEM ORG $035B ; (or anywhere else)
; SONG RMB 1
; INS_PTR RMB 1
; POS_A RMB 1
; POS_B RMB 1
; POS_C RMB 1
;; ======================================================================== ;;
;; CONSTANTS USED BY THESE ROUTINES ;;
;; ======================================================================== ;;
RF2POS EQU ((POS_A - REF_A) AND $FFFF)
POS2NT EQU ((POS_A - NOTE_A) AND $FFFF)
POS2IN EQU ((INSTR_A - POS_A) AND $FFFF)
IN2PER EQU (($01F0 - INSTR_A) AND $FFFF)
PER2CN EQU ((COUNT_A - $01F4) AND $FFFF)
V2V EQU (($01FB - VOL_A) AND $FFFF)
;; ======================================================================== ;;
;; TRKSNGINIT Song initialization ;;
;; ======================================================================== ;;
TRKSNGINIT PROC
MVI@ R5, R1 ; read song address
@@1 MVO R1, SONG ; save song address
ADDI #2, R1 ; read pointer to instruments
MVI@ R1, R1
MVO R1, INS_PTR ; and save it
CLRR R0 ; initialize variables
MVO R0, REF_M
MVO R0, COUNT_M
MVO R0, PAT
NOP
MVO R0, REF_A
MVO R0, REF_B
MVO R0, REF_C
COMR R0
MVO R0, COUNT_A
MVO R0, COUNT_B
MVO R0, COUNT_C
MVII #1, R0
MVO R0, G_FAD ; default global volume fading
ENDP
;; ======================================================================== ;;
;; TRKPATINIT Pattern initialization ;;
;; ======================================================================== ;;
TRKPATINIT PROC
MVI SONG, R4
INCR R4
MVI@ R4, R2 ; R2 = address of 1st pattern
INCR R4
MVI PAT, R0 ; R0 = position in patterns order table
ADDR R0, R4
MVI@ R4, R1 ; R1 = pattern number
TSTR R1
BPL @@pat_ok ; end of patterns ? ...
CMPI #$F000, R1 ; ... yes : stop replay ?
BEQ TRKINIT
@@restart ADDR R1, R0 ; ... no : jump to restart position ...
ADDR R1, R4
DECR R4
MVI@ R4, R1 ; ... and read again
@@pat_ok INCR R0 ; increment position
MVO R0, PAT ; in patterns order table
SLL R1, 2 ; R4 = R1 * 4 + R2
MOVR R1, R4 ; (beginning of pattern's details)
ADDR R2, R4
MVI@ R4, R0 ; init. pattern counter
MVO R0, COUNT_P
MVI@ R4, R0 ; init. position for each channel
MVO R0, POS_A
MVI@ R4, R0
MVO R0, POS_B
MVI@ R4, R0
MVO R0, POS_C
JR R5
ENDP
;; ======================================================================== ;;
;; TRKINIT Global tracker initialization ;;
;; ======================================================================== ;;
TRKINIT PROC
MVII #$38, R0 ; 'Enable Noise/Tone' register
MVO R0, $01F8
CLRR R0
MVO R0, SONG ; no song
MVII #$01F0, R4 ; clear channels periods / low
MVO@ R0, R4 ; $01F0
MVO@ R0, R4 ; $01F1
MVO@ R0, R4 ; $01F2
INCR R4 ; clear channels periods / hi
MVO@ R0, R4 ; $01F4
MVO@ R0, R4 ; $01F5
MVO@ R0, R4 ; $01F6
MVII #$01FB, R4 ; clear volumes
MVO@ R0, R4 ; $01FB
MVO@ R0, R4 ; $01FC
MVO@ R0, R4 ; $01FD
JR R5
ENDP
;; ======================================================================== ;;
;; TRKPLAY Ticks the tracker and updates the PSG ;;
;; ======================================================================== ;;
TRKPLAY PROC
PSHR R5
MVI SONG, R4 ; R4 = song base address
TSTR R4 ; is a song actually playing ?
BEQ @@done
MVI COUNT_M,R0 ; ... yes : increment global music counter
INCR R0
MVO R0, COUNT_M
MVI REF_M, R0 ; refresh notes ?
DECR R0
BPL @@notes_ok
MVI@ R4, R0 ; ... yes : read speed
MVO R0, REF_M
MVII #REF_A, R3 ; refresh note for each channel
CALL TRKCHUPD
MVII #REF_B, R3
CALL TRKCHUPD
MVII #REF_C, R3
CALL TRKCHUPD
MVI COUNT_P,R0 ; decrement pattern counter
DECR R0
MVO R0, COUNT_P
BNEQ @@upd_psg ; jump to next pattern ?
CALL TRKPATINIT ; ... yes
B @@upd_psg
@@notes_ok MVO R0, REF_M
@@upd_psg MVII #NOTE_A,R3 ; update PSG for each channel
CALL TRKPSGUPD
MVII #NOTE_B,R3
CALL TRKPSGUPD
MVII #NOTE_C,R3
CALL TRKPSGUPD
@@done PULR PC
ENDP
;; ======================================================================== ;;
;; TRKCHUPD Updates a channel ;;
;; ======================================================================== ;;
TRKCHUPD PROC
PSHR R5
MVI@ R3, R0 ; (R3 = REF_x)
SUBI #$10, R0
BMI @@ch_new
MVO@ R0, R3
PULR PC
@@ch_new ADDI #RF2POS,R3 ; read pos
MVI@ R3, R4
MVI@ R4, R0 ; read data
MOVR R0, R1 ; extra data to read ? ...
BPL @@data_ok
MVI@ R4, R2 ; ... yes : R2 = new instrument
ADDI #POS2IN,R3
MVO@ R2, R3 ; save it
SUBI #POS2IN,R3
@@data_ok MVO@ R4, R3 ; update pos
SWAP R0 ; save note
SUBI #POS2NT,R3
ANDI #$7F, R0
BEQ @@skip_sav
MVO@ R0, R3
@@skip_sav ADDI #3, R3 ; new refresh value (R3 = REF_x)
MVO@ R1, R3
ANDI #$F, R1 ; new volume
ADDI #3, R3 ; (R3 = VOL_x)
MVO@ R1, R3
TSTR R0 ; if note = 0,
BEQ @@ch_ok ; don't reset counter
ADDI #6, R3 ; (R3 = COUNT_x)
CLRR R0 ; reset counter
MVO@ R0, R3
@@ch_ok PULR PC
ENDP
;; ======================================================================== ;;
;; TRKPSGUPD Updates the PSG ;;
;; ======================================================================== ;;
TRKPSGUPD PROC
MVI@ R3, R1 ; read note
ADDI #12, R3 ; (R3 = COUNT_x)
MVI@ R3, R2 ; read channel counter -> R2
CMPI #$FF, R2 ; prevents loop after $FF
BEQ @@cnt_ok
INCR R2 ; increment counter
MVO@ R2, R3
DECR R2
@@cnt_ok CMPI #85, R1 ; drum ?
BGE @@drum
;; ------------------------------------------------------------------------ ;;
;; Standard instrument ;;
;; ------------------------------------------------------------------------ ;;
SUBI #3, R3 ; (R3 = INSTR_x)
MVI@ R3, R4 ; R4 = pointer to instrument
ADD INS_PTR,R4
PSHR R2 ; apply pitch effect
ANDI #3, R2
ADD@ R4, R2
ADD@ R2, R1
ADDI #@@nt-1,R1 ; read period from notes table
MVI@ R1, R0 ; R0 = period
ADDI #84, R1 ; read vibrato amplitude for this note
MVI@ R1, R1 ; R1 = vibrato amplitude
MVI@ R4, R2 ; R2 = type of vibrato for this channel
CMPI #1, R2
BLT @@vibr0 ; no vibrato ?
BEQ @@low_vibr ; low vibrato ?
CMPI #2, R2
BGT @@apply_vb ; high vibrato --> amplitude unchanged
SLR R1 ; medium vibrato --> 1/2 amplitude
INCR PC
@@low_vibr SLR R1, 2 ; low vibrato --> 1/4 amplitude
@@apply_vb MVI COUNT_M,R2 ; apply vibrato
ANDI #7, R2 ; according to current step
ADDI #@@v_tb,R2 ; (i.e. COUNT_M % 8)
MVI@ R2, PC
@@v_tb DECLE @@vibr1, @@vibr2 ; vibrato processing index
DECLE @@vibr0, @@vibr3
DECLE @@vibr4, @@vibr3
DECLE @@vibr0, @@vibr2
@@vibr1 SUBR R1, R0 ; - amplitude
B @@vibr0
@@vibr2 SLR R1 ; - 1/2 amplitude
SUBR R1, R0
B @@vibr0
@@vibr3 SLR R1 ; + 1/2 amplitude
ADDR R1, R0
INCR PC
@@vibr4 ADDR R1, R0 ; + amplitude
@@vibr0 ADDI #IN2PER,R3 ; write period
MVO@ R0, R3 ; (low)
SWAP R0
ADDI #4, R3
MVO@ R0, R3 ; (high)
ADDI #PER2CN,R3
PULR R0 ; R0 = channel counter / 2
SLR R0
@@env MVI@ R4, R4 ; R4 = pointer to envelope
ADD@ R4, PC ; apply speed of envelope
SLR R0
SLR R0
SLR R0
MOVR R0, R1 ; get volume from envelope
SLR R0, 2
ADDR R0, R4
MVI@ R4, R0
ANDI #3, R1
ADDI #@@e_tb,R1
MVI@ R1, PC
@@e_tb DECLE @@env0, @@env1
DECLE @@env2, @@env3
@@env1 SWAP R0
B @@env3
@@env0 SWAP R0
@@env2 SLR R0, 2
SLR R0, 2
@@env3 ANDI #$F, R0
SUBI #6, R3 ; (R3 = VOL_x)
SUB@ R3, R0 ; get volume from song
SUB G_FAD, R0 ; apply global volume fading
BPL @@upd_vol
CLRR R0
@@upd_vol ADDI #V2V, R3 ; apply new volume
MVO@ R0, R3
JR R5 ; return
;; ------------------------------------------------------------------------ ;;
;; Drum ;;
;; ------------------------------------------------------------------------ ;;
;; WARNING: drums are currently supported on channel A only ;;
;; ------------------------------------------------------------------------ ;;
@@drum CMPI #8, R2 ; end of drum ?
BGE @@end_drum
SUBI #85-3*4,R1 ; get pointer to drum data
ADD INS_PTR,R1
MVI@ R1, R4
SLL R2, 2
ADDR R2, R4
MVI@ R4, R0 ; tone period
MVO R0, $01F0
SWAP R0
MVO R0, $01F4
MVI@ R4, R0 ; noise period
MVO R0, $01F9
MVI@ R4, R0 ; enable tone / noise
MVO R0, $01F8
MVI@ R4, R0 ; volume
SUB G_FAD, R0
BPL @@drum_vol
@@end_drum CLRR R0
@@drum_vol MVO R0, $01FB
JR R5
;; ======================================================================== ;;
;; Periods of the 84 defined notes ;;
;; (from C-1 to B-7) ;;
;; ======================================================================== ;;
@@nt DECLE $0D5C, $0C9D, $0BE7, $0B3C, $0A9B, $0A02, $0973, $08EB
DECLE $086B, $07F2, $0780, $0714, $06AE, $064E, $05F4, $059E
DECLE $054D, $0501, $04B9, $0475, $0435, $03F9, $03C0, $038A
DECLE $0357, $0327, $02FA, $02CF, $02A7, $0281, $025D, $023B
DECLE $021B, $01FC, $01E0, $01C5, $01AC, $0194, $017D, $0168
DECLE $0153, $0140, $012E, $011D, $010D, $00FE, $00F0, $00E2
DECLE $00D6, $00CA, $00BE, $00B4, $00AA, $00A0, $0097, $008F
DECLE $0087, $007F, $0078, $0071, $006B, $0065, $005F, $005A
DECLE $0055, $0050, $004C, $0047, $0043, $0040, $003C, $0039
DECLE $0035, $0032, $0030, $002D, $002A, $0028, $0026, $0024
DECLE $0022, $0020, $001E, $001C
;; ======================================================================== ;;
;; Vibrato amplitude for each note above ;;
;; (30% of an half-tone) ;;
;; ======================================================================== ;;
DECLE $003B, $0038, $0035, $0032, $002F, $002C, $002A, $0028
DECLE $0025, $0023, $0021, $001F, $001E, $001C, $001A, $0019
DECLE $0018, $0016, $0015, $0014, $0013, $0012, $0011, $0010
DECLE $000F, $000E, $000D, $000C, $000C, $000B, $000A, $000A
DECLE $0009, $0009, $0008, $0008, $0007, $0007, $0007, $0006
DECLE $0006, $0006, $0005, $0005, $0005, $0004, $0004, $0004
DECLE $0004, $0003, $0003, $0003, $0003, $0003, $0003, $0002
DECLE $0002, $0002, $0002, $0002, $0002, $0002, $0002, $0002
DECLE $0001, $0001, $0001, $0001, $0001, $0001, $0001, $0001
DECLE $0001, $0001, $0001, $0001, $0001, $0001, $0001, $0001
DECLE $0001, $0001, $0001, $0000
ENDP
;; ======================================================================== ;;
;; End of File: tracker.asm ;;
;; ======================================================================== ;;