Listen first -- the same ten seconds of the song, taken from the board over Ethernet (48 kHz, 24 bits, stereo):
| the DSP chain off | soundtracker_dsp_off.wav |
| the DSP chain on: compressor and reverb | soundtracker_dsp_compressor_reverb.wav |
A Motorola 68000, Amiga's Paula and
a CIA, playing a ProTracker module with the replay
routine every Amiga demo used -- on a Digilent Arty
A7-100T, or simulated with Verilator. The Arty
has no audio output, so the sound leaves over its USB cable: 22 050
stereo frames a second on the serial line at 2 000 000 baud, and
ffplay on your PC plays it.
The 68000 is written from the Programmer's Reference Manual and checked against Musashi (every register, every instruction); Paula and the CIA are transcribed from MiSTer's Minimig-AGA and checked against a C model of themselves, bit for bit. ptplayer, the replay routine (Frank Wille, public domain), runs on this 68000 unchanged.
Then the four channels go through five studio effects in hardware -- a compressor, a chorus, a flanger, a delay and a reverb, each the twin of a C++ model and checked against it sample by sample -- while a panel on the PC moves their knobs: the DSP system.
Download l8soundtracker-dist.tar.gz, unpack it and start the menu:
tar xzf l8soundtracker-dist.tar.gz
cd l8soundtracker
make
make asks two questions, checks the programs it needs,
programs the board (the programmer, openFPGALoader, is in the archive)
and plays the song. What it needs on your PC:
| program | for | Debian / Ubuntu |
|---|---|---|
ffplay |
playing the sound | sudo apt install ffmpeg |
make, cc, c++ |
the Makefile; the simulation; the programmer, should the bundled one not run on your system | sudo apt install build-essential |
python3 |
reading the board's serial line | sudo apt install python3 |
verilator |
only for the simulation | sudo apt install verilator |
If ffplay or the compilers are missing,
make says so and prints the line to install them. Your user
needs to be allowed to use the serial port (group dialout
on Debian / Ubuntu).
| question | choices | on the command line |
|---|---|---|
| 1. What should run? | dsp (the default) -- the board with the five DSP
effects and their panel; arty --
the board without the effects, the sound from its USB cable;
verilator -- simulated on this PC |
TARGET=dsp / TARGET=arty /
TARGET=verilator |
2. Where should the sound come from? (dsp) |
usb -- the USB cable, 8 kHz mu-law, nothing to set up;
eth -- Ethernet, 48 kHz 24-bit stereo (the Ethernet setup first) |
OUT=usb / OUT=eth |
2. What should happen with the sound? (arty) |
play -- ffplay plays it; wav -- recorded
into a .wav |
OUT=play / OUT=wav |
| 3. How much music? (a .wav) | 20 s, 60 s, the whole song (212 s) | SECS=… |
make TARGET=dsp OUT=usb # the DSP system and its panel, the sound over the USB cable
make TARGET=dsp OUT=eth # the same, the sound at full quality over Ethernet
make TARGET=arty OUT=play # the plain board, the song in ffplay (Ctrl-C stops; make again starts it again)
make TARGET=arty OUT=wav SECS=60 # the plain board, one minute into soundtracker_arty.wav
make TARGET=verilator SECS=20 # simulated: 20 s into soundtracker_verilator.wav (about 5 s per second)
The USB line carries the sound as 8 kHz mu-law -- a telephone's
quality, enough to hear what a knob does. The board also sends the
chain's output at full quality over its Ethernet port: 48 000
stereo frames a second, 24 bits, exactly the numbers the
effects compute. make TARGET=dsp OUT=eth plays that. It
needs a wired connection between the Arty and the PC, set up once.
| the Arty (fixed in the bitstream) | the PC | |
|---|---|---|
| port | the RJ45 jack, 100 Mb/s (the board's PHY, MII) | any free Ethernet port; a USB-Ethernet dongle does |
| MAC address | 02:00:4c:38:00:01 |
its own |
| IPv4 address | 192.168.77.2 |
192.168.77.1, netmask /24
(255.255.255.0) |
| UDP port | 36528 (it listens and sends from there) |
any: the panel picks one and says "hello" from it |
| gateway, DNS, DHCP | none | none: the cable is a network of its own, keep your normal network on the other port |
| answers | ARP and UDP (no ping) | |
| traffic | 400 datagrams a second, 1 444 bytes each, 4.6 Mb/s to the PC | one "hello" a second until the board answers |
┌──────────────────────────────┐ ┌──────────────────────────────────────────────┐
│ Arty A7-100T │ Ethernet │ the PC: a free network port (a USB-Ethernet │
│ RJ45, 100 Mb/s ├─────────────┤ dongle does), address 192.168.77.1/24 │
│ 192.168.77.2, UDP port 36528 │ a cable │ the panel: st_gui.py --eth │
└──────────────────────────────┘ └──────────────────────────────────────────────┘
The cable: from the Arty's RJ45 jack to a network port of the PC that is not your normal network (a USB dongle is the easy way). The jack's link LEDs light when both ends are on.
The port's name: ip -br link lists
them; the new one appears when the dongle is plugged in (for example
enx001122334455 or eth1).
The address (lost at a reboot):
sudo ip addr add 192.168.77.1/24 dev PORT
sudo ip link set PORT up
If NetworkManager runs on the PC, it may take the port back; make it stay with a profile of its own instead:
nmcli con add type ethernet ifname PORT con-name arty ipv4.method manual ipv4.addresses 192.168.77.1/24 ipv6.method ignore
nmcli con up artyCheck: with the DSP system on the board,
ip neigh show 192.168.77.2 names the Arty's address
02:00:4c:38:00:01 after the panel's first packet. The board
answers ARP and UDP only -- ping gets no answer, which is
fine.
Play: make TARGET=dsp OUT=eth. The
panel sends "hello" to 192.168.77.2, port 36528, once a second until the
board answers; the board then sends every datagram to that sender: 400 a
second, 4.6 Mb/s, each 120 frames. A firewall that allows answers to the
PC's own UDP packets (the usual setting) lets them in.
If make stops with "no network port of this machine has
the address 192.168.77.1", step 3 is missing. If the panel runs but
stays silent, ip neigh shows whether the board answered,
and sudo tcpdump -ni PORT udp port 36528 whether the
datagrams arrive. The same datagrams carry the frame that went INTO the
chain beside each that came out -- what the repository's
probe_check.py uses to compare the board with the C++
models.
"Neon Techno" by Drozerix (3:32 of 4-channel ProTracker, 61 KB), released into the public domain -- its page on The Mod Archive. It is in the archive as mod/drozerix_-_neon_techno.mod and inside the bitstream, in the block RAM beside the program.
┌────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Digilent Arty A7-100T │
│ ┌──────────────────────────────────────────────────────────────────┐ ┌─────────────────────────┐ │
│ │ the system, 25 MHz st_soc.l8 │ │ the board, 100 MHz │ │
│ │ │ │ │ │
│ │ ┌───────────────────┐ bus: 24-bit address, 16-bit data │ │ ┌─────────────────────┐ │ │
│ │ │ 68000 │ AS R/W UDS LDS ─────┬────────────┐ │ │ │ async FIFO │ │ │
│ │ │ 68k.l8 ├──────────────────────┤ │ │ │ │ 25 -> 100 MHz │ │ │
│ │ │ IPL2-0 <── Paula │ │ │ │ │ └──────────┬──────────┘ │ │
│ │ └───────────────────┘ │ │ │ │ │ │ │
│ │ ┌──────────────────────────────────────┐ │ │ │ │ ┌──────────┴──────────┐ │ │
│ │ │ block RAM 128 KB 0x000000 ├───┘ │ │ │ │ UART 2 000 000 baud │ │ │
│ │ │ vectors, boot.s, ptplayer, │ │ │ │ │ 8N1, S / Q commands │ │ │
│ │ │ the module (Neon Techno, 61 KB) │ │ │ │ └──────────┬──────────┘ │ │
│ │ └──────────────────┬───────────────────┘ │ │ │ │ │ │
│ │ │ audio DMA (Agnus's slots) │ │ └────────────┼────────────┘ │
│ │ ┌──────────────────┴─────────────────────────────────┐ │ │ │ │
│ │ │ custom chips 0xDFF000 st_chips.l8 ├──┘ │ ┌────────────┴────────────┐ │
│ │ │ INTENA INTREQ DMACON VHPOSR the beam │ │ │ FTDI FT2232H, USB │ │
│ │ │ ┌────────────────────────────────────────────────┐ │ │ │ channel B: /dev/ttyUSB1 │ │
│ │ │ │ Paula: 4 audio channels st_paula.l8 │ │ │ └────────────┬────────────┘ │
│ │ │ │ AUDxLC LEN PER VOL DAT, states 0 1 2 3 4 │ │ │ │ │
│ │ │ └────────────────────────────────────────────────┘ │ │ │ │
│ │ │ ┌────────────────────────────────────────────────┐ │ │ │ │
│ │ │ │ CIA-B 0xBFD000: timer A (tempo), timer B, │ │ │ │ │
│ │ │ │ ICR -> INTREQ EXTER, level 6 (ptplayer) │ │ │ │ │
│ │ │ └────────────────────────────────────────────────┘ │ │ │ │
│ │ │ ┌────────────────────────────────────────────────┐ │ │ │ │
│ │ │ │ mixer: L = 0 + 3, R = 1 + 2; box filter │ │ │ │ │
│ │ │ │ 3 546 895 Hz -> 22 050 Hz, s16 stereo frames ├─┼──────────>│ │ │
│ │ │ └────────────────────────────────────────────────┘ │ │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │ │
│ └──────────────────────────────────────────────────────────────────┘ │ │
└─────────────────────────────────────────────────────────────────────────────────────┼──────────────┘
│ USB
┌────────────────┴────────────┐
│ the PC │
│ st_play.py -> ffplay │
└─────────────────────────────┘
boot.s, ptplayer and the module. It stays writable --
ptplayer keeps its variables beside its code, as on the Amiga, and
patches the module's header -- so after a restart the image is what the
program left, as chip RAM is.paula_audio_channel.v and agnus_audiodma.v,
transcribed: four channels, each a state machine over its period and
length counters, the DMA requests latched at the start of a line and
served in each channel's slot. It steps on the PAL colour clock, 3 546
895 Hz, made by a phase accumulator from 25 MHz.S restarts the song (the 68000's reset pin)
and starts the stream at a frame's boundary, Q stops it.
tools/st_play.py sends Q, drains the line,
sends S.The 68000 reaches the sound hardware as memory: word registers at
0xDFF000 + offset (the custom chips) and byte registers on
the even addresses of 0xBFD000 (CIA-B). The program's only
view of Paula is these writes.
One audio channel -- four of them, x =
0..3, at 0xDFF0A0 + 0x10 * x:
| offset | register | width | meaning |
|---|---|---|---|
+0x0 |
AUDxLCH |
3 bits | the sample's address, bits 20..16 |
+0x2 |
AUDxLCL |
15 bits | the sample's address, bits 15..1 (a sample starts on a word) |
+0x4 |
AUDxLEN |
16 bits | the sample's length in WORDS (two samples a word) |
+0x6 |
AUDxPER |
16 bits | the period: colour clocks per sample (3 546 895 Hz / period = the sample rate; 124 at the least) |
+0x8 |
AUDxVOL |
7 bits | the volume, 0..64 |
+0xA |
AUDxDAT |
16 bits | two 8-bit signed samples; written by the DMA, or by the CPU for sound without DMA |
Shared, each a set / clear register: bit 15 of a write says whether the ones in bits 14..0 SET or CLEAR their bits.
| address | register | the bits used here |
|---|---|---|
0xDFF096 (write) / 0xDFF002 (read) |
DMACON / DMACONR |
9 DMAEN (the master switch), 0..3
AUD0EN..AUD3EN (a channel's DMA) |
0xDFF09A / 0xDFF01C |
INTENA / INTENAR |
14 INTEN (the master switch), 7..10
AUD0..AUD3 (level 4), 13 EXTER
(level 6) |
0xDFF09C / 0xDFF01E |
INTREQ / INTREQR |
the same bits: what is pending; written with bit 15 clear to acknowledge |
0xDFF006 |
VHPOSR |
the beam's horizontal position (read only) |
CIA-B, the timers ptplayer runs on. They count the E clock (709 379 Hz, a fifth of the colour clock):
| address | register | meaning |
|---|---|---|
0xBFD400 / 0xBFD500 |
TALO / TAHI |
timer A's start value: the song's tempo (1 773 447 / BPM; 14 187 at 125 BPM = 50 calls a second) |
0xBFD600 / 0xBFD700 |
TBLO / TBHI |
timer B's start value: 576 E-clocks, the time Paula needs to start a new sample |
0xBFDE00 / 0xBFDF00 |
CRA / CRB |
bit 0 start, bit 3 one-shot, bit 4 load (0x11: A
continuous; 0x19: B once) |
0xBFDD00 |
ICR |
written: bit 7 set / clear, bits 0 / 1 enable timer A / B; read: what happened, and the read clears it |
ptplayer's interrupt runs 50 times a second (timer A). A new note on channel 0, as the board's write trace has it -- the first note of "Neon Techno", 1 595 words of sample at period 113 (31 388 samples a second):
dff096 = 0001 DMACON: channel 0's DMA off (the old sample stops)
dff0a0 = 0000 AUD0LCH \
dff0a2 = a288 AUD0LCL > the new sample at 0x00a288
dff0a4 = 063b AUD0LEN: 1 595 words
dff0a6 = 0071 AUD0PER: 113 colour clocks a sample
dff0a8 = 0040 AUD0VOL: 64
bfdf00 = 19 CIA-B CRB: timer B, once, 576 E-clocks (0.8 ms)
... level 6, timer B ...
dff096 = 800b DMACON: channels 0, 1 and 3 on -- Paula fetches and plays
... level 6, timer B again ...
dff09c = 2580 INTREQ: acknowledge the audio blocks that started
dff0a0 = 0000 AUD0LC / AUD0LEN: where the sample LOOPS -- Paula takes these
dff0a2 = ab4c at the end of the block it is playing now
dff0a4 = 01d9
Paula reloads AUDxLC and AUDxLEN at the
start of every block, so the pointers the program writes while a block
plays are the NEXT block's: the loop of a sample, or a silent word to
stop.
┌─────────────────────────────┐ ┌───────────────────────────────────────────────────────────┐
│ the 68000 │ writes │ one channel st_paula.l8 │
│ ptplayer, 50 times a second ├───────>│ │
└─────────────────────────────┘ │ ┌─────────────────────┐ ┌──────────────────────────┐ │
│ │ AUDxLC AUDxLEN ├─────>│ DMA: pointer and counter │ │
┌─────────────────────────────┐ │ └─────────────────────┘ │ a word a line at most, │ │
│ block RAM: the samples │ a word │ │ in this channel's slot │ │
│ (the module) ├───────>├─────────────────────────────>│ (hpos 0x0E + 2x) │ │
└─────────────────────────────┘ │ └─────────────┬────────────┘ │
│ │ AUDxDAT │
│ ┌─────────────────────┐ ┌─────────────┴────────────┐ │
│ │ AUDxPER ├─────>│ period counter: every │ │
│ └─────────────────────┘ │ PER colour clocks the │ │
│ │ next byte: high, then low│ │
│ └─────────────┬────────────┘ │
│ │ 8-bit sample │
│ ┌─────────────────────┐ ┌─────────────┴────────────┐ │
│ │ AUDxVOL ├─────>│ x volume (64 as 63) │ │
│ └─────────────────────┘ └─────────────┬────────────┘ │
└────────────────────────────────────────────┼──────────────┘
│ 14 bits, signed
v the mixer
The channel is a state machine of five states, stepped on every
colour clock (Minimig's paula_audio_channel.v). States 3
and 4 alternate for as long as the DMA is on; at the end of a block
(AUDxLEN words) the pointer and the length are taken again
from AUDxLC and AUDxLEN, and the interrupt
says that a new block has begun:
┌────────────┐ DMA on ┌──────────────────┐ a word ┌──────────────────┐ a word ┌───────────────────┐
│ 0 idle ├──────────>│ 1 started: the ├───────>│ 2 the first word ├───────>│ 3 the high byte │
│ │ │ word is thrown │ │ of the block; │ │ plays PER clocks │
│ │<──────────┤ away, the │ │ the interrupt │ │ │
└────────────┘ DMA off │ pointer reloads │ │ (AUDx, level 4) │ │ │
└──────────────────┘ └──────────────────┘ └──┬─────────┬──────┘
│ ^
PER clocks │ │ PER clocks:
v │ the next word
┌──┴─────────┴──────┐
│ 4 the low byte │
│ plays PER clocks │
└───────────────────┘
┌──────────────┐
│ channel 0 ├────┐
└──────────────┘ │ ┌────────────────┐ ┌─────────────────────────────────┐ ┌─────────────────────┐
┌──────────────┐ ├──>│ left = 0 + 3 ├──>│ box filter, 3 546 895 Hz to ├──>│ a frame: left, │
│ channel 3 ├────┘ └────────────────┘ │ 22 050 Hz: each colour clock │ │ right (s16, x2) │
└──────────────┘ │ weighted 22 050, the one that │ └──────────┬──────────┘
┌──────────────┐ │ crosses a frame's edge split; │ │ 4 bytes
│ channel 1 ├────┐ ┌────────────────┐ │ a frame = the mean of the 160.86│ ┌──────────┴──────────┐
└──────────────┘ ├──>│ right = 1 + 2 ├──>│ colour clocks it spans │ │ UART 2 000 000 baud │
┌──────────────┐ │ └────────────────┘ └─────────────────────────────────┘ │ -> st_play.py │
│ channel 2 ├────┘ │ -> ffplay │
└──────────────┘ └─────────────────────┘
The Amiga's own sound leaves through a DAC and an analogue low-pass filter. Here the box filter is both: it turns the samples, each held for its period, into frames at a fixed rate, and it averages away what is above 11 kHz, where 22 050 frames a second can not follow.
The second system on the same board
(arty/st_dsp_arty.l8) sends the four channels through a
chain of five audio effects before they leave: a
compressor, a chorus, a
flanger, a delay and a
reverb, each a hardware twin of a C++ model of a studio
effect. The PC switches them on and off and moves their knobs while the
song plays. The sound leaves twice: over the USB cable as 8 kHz mu-law
for listening anywhere, and over Ethernet as the full 48 kHz 24-bit
stereo -- together with the frame that went INTO the chain, so the PC
can check every frame against the models. make TARGET=dsp
programs it and opens the panel.
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Digilent Arty A7-100T arty/st_dsp_arty.l8 │
│ ┌──────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ the system, 25 MHz │ │
│ │ ┌───────────────────┐ 48 000 frames/s ┌─────────────────────────────────────────────────┐│ │
│ │ │ 68000, Paula, │ s16 stereo │ st_dsp_sys st_dsp.l8 ││ │
│ │ │ CIA-B, the mixer, ├────────────────>│ ┌───────────┐ ┌─────────────────────────────┐ ││ │
│ │ │ box filter 48 kHz │ │ │ fifo, 16 ├>┤ the chain (Q1.23 stereo) │ ││ │
│ │ │ st_soc.l8 │ │ │ frames │ │ compressor, chorus, flanger,│ ││ │
│ │ └───────────────────┘ │ └─────┬─────┘ │ delay, reverb │ ││ │
│ │ │ │ └──────────────┬──────────────┘ ││ │
│ │ │ │ in │ out ││ │
│ │ │ ┌─────┴──────────────────────┴──────────────┐ ││ │
│ │ │ │ frame_probe: (in, out) pairs, 96 bits │ ││ │
│ │ │ └─────────────────────┬─────────────────────┘ ││ │
│ │ │ ┌─────────────────────┼─────────────────────┐ ││ │
│ │ │ │ mono, resample 48 -> 8 kHz, s16, mu-law │ ││ │
│ │ │ └─────────────────────┼─────┬───────────────┘ ││ │
│ │ ┌──────────────────────────────────┐ │ ┌─────────────────────┼─────┼───────────────┐ ││ │
│ │ │ st_cmd: S, Q, the records ├─>│ │ dsp_param_bus, five dsp_param_reg: the │ ││ │
│ │ └────────────────┬─────────────────┘ │ │ coefficients, double buffered │ ││ │
│ │ │ │ └─────────────────────┼─────┼───────────────┘ ││ │
│ │ │ └───────────────────────┼─────┼───────────────────┘│ │
│ │ │ ┌───────────────────────┴───┐ │ │ │
│ │ │ │ probe_pack: 120 pairs a │ │ │ │
│ │ │ │ datagram; udp4_port │ │ │ │
│ │ │ └─────────────┬─────────────┘ │ │ │
│ └──────────────────┼──────────────────────────────────┼───────────────┼────────────────────┘ │
│ ┌──────────────────┴────────┐ ┌───────────────────────┴───┐ ┌─────────┴─────────────────┐ │
│ │ UART rx 115 200 baud │ │ Ethernet 100 Mb/s (MII) │ │ UART tx: 8 000 bytes/s │ 100 MHz │
│ └──────────────────┬────────┘ └───────────────────────┬───┘ └─────────┬─────────────────┘ │
└────────────────────┼──────────────────────────────────┼───────────────┼──────────────────────────────────┘
│ USB │ RJ45 │ USB
┌────────────────────┴──────────────────────────────────┴───────────────┴──────────────────────────────────┐
│ the PC: st_gui.py -- the knobs (libstdsp packs them), ffplay plays 48 kHz from Ethernet or 8 kHz mu-law │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────┘
dsp_bypass), and the
unit's state -- its delay lines, its LFO -- stands still until it is
switched on again.The PC talks to the board over the same USB serial line the sound leaves on, at 115 200 baud. Four commands:
| bytes | command |
|---|---|
S |
start: the 68000 restarts the song (its reset pin, 255 clocks), the stream starts at a frame's edge |
Q |
stop the stream |
W idx lo hi b0 ..
b(n-1) |
write the n = lo + 256 hi bytes of effect idx's record (0 compressor .. 4 reverb) |
C mask |
commit: every record written so far takes effect at once; bit k of mask switches effect k on |
A record is not a list of knobs: it is the unit's
coefficients, computed on the PC. What a knob means --
a time in ms that becomes a delay in samples, a decay time in seconds
that becomes a feedback gain, a cut-off in Hz that becomes five biquad
coefficients -- is computed in double precision by the same code the
models use (*_ctl.h beside each unit), quantized, and
packed bit by bit into the record (libstdsp's
stdsp_pack). The hardware only multiplies and adds.
┌───────────────────────────┐ ┌───────────────────────────┐ ┌───────────────────────────────────────┐
│ st_gui.py: a slider moves ├──>│ libstdsp: the effect's ├──>│ W idx len record bytes, LSB first │
│ (13 knobs of the reverb) │ │ *_ctl.h -> coefficients, │ │ C mask │
└───────────────────────────┘ │ quantized, packed │ └───────────────────┬───────────────────┘
└───────────────────────────┘ │ UART
┌───────────────────────────────────────────────────────────────────────────────────┴───────────────────┐
│ st_cmd: S and Q only while no record is being received (a record's bytes may be 'S' or 'Q') │
└───────────────────────────────────────────────────┬───────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────┴───────────────────────────────────────────────────┐
│ dsp_param_bus: idle -> idx -> len lo -> len hi -> data (n bytes) -> idle; 'C' -> mask -> commit │
└───────────┬───────────────────────────────────────────────────────────────────────────────┬───────────┘
│ a byte for idx k │ commit
┌───────────┴───────────────────────────────────────────────────────────────────────────────┴───────────┐
│ dsp_param_reg<BITS, k>, one per effect │
│ ┌─────────────────────────────────────────┐ commit ┌─────────────────────────────────────────────┐ │
│ │ shadow: the bytes shift in ├──────────>│ live: what the unit uses, cast to its │ │
│ │ │ │ struct (CompCoef .. ReverbCoef) │ │
│ └─────────────────────────────────────────┘ └─────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────────────────────────────────────┘
Double buffered: the record shifts into a shadow
while the unit goes on with the old one; C copies all five
shadows into their live registers in the same clock, so a unit never
runs on a half-written record, and two effects changed together change
in the same frame.
| effect | record | knobs |
|---|---|---|
| compressor | 244 bits | input, threshold, ratio, attack, release, knee, make-up, mix, auto, relax, character, mode |
| chorus | 266 bits | x (time), y (spread), speed, depth, mix |
| flanger | 2 116 bits | character, alternate, stereo, manual, colour, feedback, speed, depth, mix |
| delay | 1 008 bits | time, scaler, detune, stereo detune, low cut, high cut, feedback, threshold, level mode, crossfeed, mix |
| reverb | 1 039 bits | hall, size, pre-delay, diffusion, buildup, low factor, low split, time, high factor, high split, late mix, width, mix |
make TARGET=dsp programs the Arty with the DSP system
(bit/top_dsp.bit), builds the panel's packer the first time
(tools/stdsp/ into tools/libstdsp.so: the
effects' control code, the same the models use), and starts the panel,
tools/st_gui.py -- a terminal page with every effect's
switch and knobs. ffplay plays the song behind it; every effect starts
switched off, at the knobs' defaults.
| key | what it does |
|---|---|
| up / down | the next / previous row: an effect's first row is its switch, the rows below are its knobs |
| left / right | move the knob down / up by its step |
| shift + left / right | move it by a fifth of the step |
| space | switch the effect on / off |
| tab | the next effect |
| r | restart the song (and the stream) |
| q, Esc | quit (the stream stops) |
The header line shows how many bytes a second come from the USB line (8 000 while the stream runs). Beside an effect's name: the size of the record it last sent, or "refused by the unit (out of its range)" when the hardware cannot do a setting -- a delay longer than its line, a mode the effect does not have; the old record then stays in force.
A moved knob takes effect at once: the panel computes the effect's
coefficients from all its knobs, sends the record (W) and a
commit (C) -- 30 to 270 bytes, a few to 25 ms on the line,
without a gap in the sound.
For the sound in full quality the board and the PC need an Ethernet cable between them: the Ethernet setup.
Soundtracker meets DSP -- the DSP pipeline 8000 bytes/s from the board (q quit, r restart, tab next effect, space on/off)
[x] compressor 31 bytes
input dB [############------------] 0
threshold dB [###############---------] -12
> ratio [#-----------------------] 2
attack s [###---------------------] 0.0244
release s [#-----------------------] 0.125
knee [####--------------------] 0.17
makeup dB [########----------------] 0
mix [########################] 1
auto [------------------------] 0
relax [------------------------] 0
character [------------------------] 0
mode [------------------------] 0
[ ] chorus 34 bytes
x (time) [############------------] 0.5
y (spread) [############------------] 0.5
speed [########----------------] 0.3333
depth [############------------] 0.5
mix [############------------] 0.5
[ ] flanger 265 bytes
...
[ ] delay 126 bytes
...
[ ] reverb 130 bytes
...
compressor
| knob | range | step (shift: a fifth) | default | what it does |
|---|---|---|---|---|
| input dB | -24 .. 24 | 1 | 0 | gain before the compressor |
| threshold dB | -40 .. 6 | 1 | -12 | the level above which the gain is turned down |
| ratio | 1 .. 20 | 0.5 | 2 | how much: 4 means 4 dB over the threshold come out as 1 dB |
| attack s | 0.001 .. 0.2 | 0.001 | 0.0244 | how fast the gain goes down when the level rises |
| release s | 0.01 .. 2 | 0.01 | 0.125 | how fast the gain comes back when the level falls |
| knee | 0 .. 1 | 0.05 | 0.17 | the soft knee's width: 0 sharp (10 dB), 1 wide (40 dB) |
| makeup dB | -12 .. 24 | 1 | 0 | gain after the compressor |
| mix | 0 .. 1 | 0.05 | 1 | dry / compressed: below 1 is parallel compression |
| auto | 0 .. 1 | 0.05 | 0 | auto timing: attack and release follow the material |
| relax | 0 .. 1 | 0.05 | 0 | less gain reduction overall |
| character | 0 .. 5 | 1 | 0 | 0 Vanilla, 1 Smooth, 2 Over, 3 Glue, 4 Resist, 5 Smash: the slope and the times scaled |
| mode | 0 .. 1 | 1 | 0 | 0 Standard, 1 Beyond (ratios below 1 expand, past infinity invert) |
chorus
| knob | range | step (shift: a fifth) | default | what it does |
|---|---|---|---|---|
| x (time) | 0 .. 1 | 0.05 | 0.5 | the voices' base delay: 20 to 60 ms |
| y (spread) | 0 .. 1 | 0.05 | 0.5 | the width: the two voices' LFO phase apart and panned apart |
| speed | 0 .. 1 | 0.05 | 0.3333 | the LFO's speed: 0.05 to 1 Hz |
| depth | 0 .. 1 | 0.05 | 0.5 | how far the delay swings: up to 20.7 ms |
| mix | 0 .. 1 | 0.05 | 0.5 | dry / wet |
flanger
| knob | range | step (shift: a fifth) | default | what it does |
|---|---|---|---|---|
| character 0 DP 2 TFX | 0 .. 2 | 2 | 2 | 0 DP, 2 TFX: the two flanger characters built (their filters and delay laws) |
| alternate | 0 .. 1 | 1 | 0 | Alternate: the loop's (and for DP the output's) sign inverted -- the hollow, through-zero sound |
| stereo | 0 .. 1 | 1 | 1 | 1: the right channel's LFO inverted (the comb moves the other way) |
| manual | 0 .. 0 | 1 | 0 | Manual Override: not in the hardware (stays 0) |
| color | 0 .. 1 | 0.05 | 0.5 | the comb's centre: where the sweep sits |
| feedback | 0 .. 1 | 0.05 | 0.5 | feedback: how strongly the comb rings |
| speed Hz | 0 .. 10 | 0.1 | 0.5 | the LFO's speed |
| depth | 0 .. 1 | 0.05 | 0.5 | how far the sweep goes |
| mix | 0 .. 1 | 0.05 | 0.5 | dry / wet |
delay
| knob | range | step (shift: a fifth) | default | what it does |
|---|---|---|---|---|
| time ms | 1 .. 165 | 1 | 120 | the delay time (up to 165 ms: the hardware's line is 170 ms) |
| scaler | 0 .. 1 | 0.05 | 0 | the time times 1 + scaler (dotted and longer feels) |
| detune ms | -5 .. 5 | 0.1 | 0 | the right channel later (or earlier) than the left |
| stereo detune | 0 .. 1 | 1 | 0 | 1: the detune goes the other way on the right |
| low cut Hz | 20 .. 1000 | 10 | 69.7 | a high-pass in the feedback: the repeats lose their lows |
| high cut Hz | 1000 .. 1.6e4 | 100 | 3083 | a low-pass in the feedback: the repeats get darker |
| feedback | 0 .. 1.2 | 0.05 | 0.6 | how much of each repeat comes back: above 1 it grows until the limiter holds it |
| threshold dB | -30 .. 6 | 1 | 0 | the limiter's level in the loop |
| level mode | 0 .. 1 | 1 | 0 | the limiter: 0 soft (tanh), 1 hard clip |
| crossfeed | 0 .. 1 | 1 | 0 | 1: each channel's repeats feed the other's line (ping-pong) |
| mix | 0 .. 1 | 0.05 | 0.333 | dry / wet |
reverb
| knob | range | step (shift: a fifth) | default | what it does |
|---|---|---|---|---|
| hall | 0 .. 1 | 1 | 0 | 0 Room, 1 Hall: the early reflections' pattern and the late diffusion |
| size | 0.5 .. 2 | 0.05 | 0.9931 | the room's size: all delays scaled 0.5 to 2 |
| pre-delay ms | 0 .. 100 | 1 | 4 | time before the reverb starts |
| diffusion | 0 .. 1 | 0.05 | 0.8 | how dense the first reflections are smeared |
| buildup | 0 .. 1 | 0.05 | 0.64 | how fast the late tail builds up |
| low factor | 0.5623 .. 1.778 | 0.02 | 0.7295 | how much faster (below 1) or slower the lows decay than the rest |
| low Hz | 50 .. 1000 | 10 | 298.5 | the split between lows and the rest |
| time s | 0.3162 .. 31.62 | 0.1 | 1.259 | the decay time (RT60) of the tail: 0.32 to 31.6 s |
| high factor | 0.5623 .. 1.778 | 0.02 | 0.717 | how much faster (below 1) or slower the highs decay |
| high Hz | 1000 .. 1.6e4 | 100 | 4003 | the split between the rest and highs |
| late mix | 0 .. 1 | 0.05 | 0.39 | early reflections / late tail |
| width | 0 .. 1 | 0.05 | 0.8475 | stereo width of the reverb: 0 mono, 1 wide |
| mix | 0 .. 1 | 0.05 | 0.585 | dry / wet |
┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐
in ──>│ compressor ├──>│ chorus ├──>│ flanger ├──>│ delay ├──>│ reverb ├──> out
│ 8 clocks │ │ 19 clocks │ │ 180 clocks │ │ 47 clocks │ │ 130 clocks │
└────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────────┘
each behind dsp_bypass(en[k]): switched off, the frame goes round it; a frame every 520 clocks
Every unit is built the same way, and the way is the reason so much fits on a small FPGA:
sm) per unit. A
frame arrives on a stream (i.each), the states walk through
the algorithm a step a clock, the last state offers the frame on the
output stream. A frame comes every 520 clocks, so a unit has hundreds of
clocks for work a C++ loop does in one pass -- the units use them to
share: one read port of one block RAM for every line of the unit, one
interpolator for both voices, one all-pass for fourteen all-passes.pow, an
exp of a knob, a sin of a cut-off: those are
the record's fields. What must be computed per sample -- a logarithm in
the compressor, a sine in an LFO -- is a table and a linear
interpolation.fx<I,F>: I integer
bits with the sign, F fraction bits), with each signal's width chosen
from the peaks measured on the song through the model. At a multiplier a
signal is rounded to at most 25 bits and a coefficient to 18 (one
DSP48E1) or kept at 32 (two DSP48E1 on the cascade, where precision
needs it).gen/gen_vectors.cpp beside each unit), with a budget in dB
below the signal; then the board over Ethernet (see How the DSP was checked).What it does. It follows the level of the louder
channel and turns the gain down when the level is above a threshold,
with a ratio, a soft knee, attack and release times -- the model
Compressor.hpp:
x = in * inG the input gain
a = max(|xl|, |xr|) the detector: peak of both channels
env = env + (a > env ? cA : cR) * (a - env) attack / release one-pole
L = 20 log10(env) + cal the level in dB
x = L - thr above the threshold
GR = 0 if x < -W/2 the gain computer: soft knee of width W
s * (x + W/2)^2 / 2W if |x| <= W/2
s * x if x > W/2, s = 1 - 1/ratio
out = (1 - mix) * x + mix * x * 10^(-GR/20) * makeup
In the hardware the decibels are replaced by powers
of two (1 unit = 6.02 dB), so the logarithm and its inverse become
log2 and 2^x, each a 258-entry table and a
linear interpolation (logexp.l8, error below 3e-6). The PC
folds every constant into the record: the threshold, the knee's width,
the slopes, the dry and wet gains.
┌──────────┐ ┌───────────┐ ┌────────────────┐ ┌────────────┐ ┌──────────────┐ ┌───────────┐ ┌─────────┐
│ idle │ │ env │ │ lvl │ │ law │ │ gain │ │ gain2 │ │ out │
│ x * ing ├─>│ one-pole, ├─>│ X2 = log2(env) ├─>│ the knee: ├─>│ e2 = 2^-GR2 ├─>│ gm = dm + ├─>│ x * gm, │
│ a = max │ │ ca or cr │ │ - t2 (table) │ │ 0, ks X2, │ │ (table) │ │ wg * e2 │ │ give │
│ |l|, |r| │ │ │ │ │ │ kq(X2+w2)^2│ │ │ │ │ │ │
└──────────┘ └───────────┘ └────────────────┘ └────────────┘ └──────────────┘ └───────────┘ └─────────┘
Eight states, eight clocks a frame; every product has its own
multiplier (the unit is small and short), the states only decide which
register takes which result -- so a long chain of logic (table,
interpolation, product) is cut into clocks. The envelope is
ufx<5,40>: a release of two seconds moves it by 2^-17
of itself a sample, which 40 fraction bits still see. Not built: the
model's Dual mode and its gain smoothing (zero in every preset).
What it does. Two copies of the input, each read
from a delay line at a delay that swings slowly around 20 to 60 ms,
panned apart and mixed with the dry signal -- the model
Chorus.hpp, measured from the reference:
x = (l + r) / 2 mono into the line
la = sin(2 pi ph), lb = sin(2 pi (ph + off)) two LFOs, phase offset = width / 2
lpa, lpb = one-pole(la, lb), 0.397 Hz the LFOs smoothed
va = line[w - (base + amp * lpa)] two voices, fractional delays,
vb = line[w - (base + amp * lpb)] 4-point Catmull-Rom interpolation
outL = gd x + g aL va + g bL vb the voices panned (aL, aR, bL, bR)
outR = gd x + g aR va + g bR vb
In the hardware one block RAM of 8 192 words (170 ms at 48 kHz, 18 bits a word) is the line. Both voices share its one read port, one address computation and one interpolator: the state machine reads the four samples around voice A's position, interpolates, then does the same for voice B.
┌────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────────────────────────────────────────┐ ┌────────┐
│ idle │ │ wr │ │ lfo │ │ for voice A, then voice B: │ │ out │
│ x = mean ├─>│ x into the ├─>│ both one- ├─>│ ┌─────────┐ ┌──────────────┐ ┌──────────┐ ├─>│ six │
│ of l, r │ │ line; both │ │ poles step │ │ │ pos ├─>│ rd x 4: i-1, ├─>│ voice: │ │ │ prod., │
│ │ │ sines (table│ │ │ │ │ d = base│ │ i, i+1, i+2 │ │ cubic4 │ │ │ give; │
│ │ │ + interp.) │ │ │ │ │ + amp lp│ │ a word/clock │ │ (s0..s3, │ │ │ ph += │
│ │ │ │ │ │ │ └─────────┘ └──────────────┘ │ frac) │ │ │ inc │
│ │ │ │ │ │ │ └──────────┘ │ │ │
└────────────┘ └─────────────┘ └─────────────┘ └──────────────────────────────────────────────┘ └────────┘
lfo.l8). 40 bits, not 32: at 32 bits the speed is off
by 1e-5, the phase drifts away from the model's over a minute, and the
voices' positions with it (70 dB below the signal at 32 bits, 95 dB at
40).fracread.l8,
cubic4) is Catmull-Rom in Horner form: its three
coefficients are shifts and adds of the four samples, then three
multiplies by the fraction.What it does. A short delay (a few ms) swept by an
LFO and fed back into itself: the comb filter it makes moves up and down
the spectrum -- the model Flanger.hpp (characters TFX and
DP), per channel:
lfo = one-pole(triangle(ph), 38 Hz) a triangle LFO, its corners rounded
dt = ct + bt * lfo, dl = cl + bl * lfo two read positions (wet, loop), linear in the LFO
dry = fir8(highpass(x)) the dry path: one biquad, an 8-tap FIR
h = biquad x 4 (x) the wet input's tone: four biquads
fbv = biquad x nloop (line[w - dl]) the loop's tone: one (DP) or three (TFX) biquads
wet = line[w - dt] 16-tap windowed-sinc fractional read
line[w] = gin * h + gfb * fbv into the line: the input and the feedback
early = one-pole(x)
out = gd * dry + gw * wet + ge * early
The fractional read is the reference engine's own: a Kaiser-windowed sinc of 16 taps (beta 8.44, cut-off 0.85 of Nyquist), its taps from a table of 257 phases, linearly interpolated between two phases. The table (4 112 entries) is computed when the hardware is elaborated, from the same formula the model uses.
In the hardware everything that repeats is one engine, and the state machine walks both channels through it:
┌───────────┐ per channel, left then right:
│ idle │ ┌───────────────┐ ┌─────────────┐ ┌───────────────┐ ┌─────────────┐ ┌───────────────┐
│ LFO step, ├─>│ sec: dry HP ├─>│ fir, firm: ├─>│ sec x 4: the ├─>│ rpos, rd ├─>│ sec x nloop: │
│ ph += inc │ │ (5 clocks) │ │ 8 taps │ │ wet input │ │ x 16: loop │ │ the loop's │
└───────────┘ └───────────────┘ └─────────────┘ └───────────────┘ │ read at dl │ │ tone │
└─────────────┘ └───────┬───────┘
┌───────────┐ ┌───────────────────────────────────────┐ ┌──────────────────────────┐ │
│ give │<─┤ wr: line[pos, c] = gin h + gfb fb; │<─┤ rpos, rd x 16: wet read │<─────────┘
│ pos += 1 │ │ early; y = gd dry + gw wet + ge early │ │ at dt │
└───────────┘ └───────────────────────────────────────┘ └──────────────────────────┘
biquad.l8) is ONE
multiplier stepped five times a section (b0 x, b1 x, a1 y, b2 x, a2 y;
transposed direct form II): one 37 x 40 bit product is six DSP48E1, five
of them in parallel were thirty. Its coefficients keep 32 fraction bits
-- DP's dry high-pass has its poles at 0.99976, where 27 bits left the
unit 75 dB off the model; with 32 it is 120 dB.What it does. Two lines, left and right, read back
after a time set in ms, with a filter in the feedback and a soft clipper
keeping the loop from running away -- the model
DelayPlus.hpp (pattern Stereo):
raw = line[w - d] 16-tap windowed-sinc fractional read (as the flanger)
y = g(lp(hp(raw))) low cut, high cut, a fixed 11.96 kHz low-pass:
three bilinear one-poles
line[w] = level(x + feedback * y) level: T tanh(v / T) (soft) or clip to T (hard)
(crossfeed: left's y into right's line, and back)
out = dry * x + wet * y
The time, the detune between the channels and the scaler become an
integer delay and a 16-tap kernel on the PC (the kernel is computed
exactly, not from a table: delay_ctl.h sums the Bessel
series), so the hardware only reads and multiplies. The clipper's
1/T comes in the record too, so there is no divider.
┌───────────┐ ┌─────────────────────────────────┐ ┌────────────────┐ ┌────────────┐ ┌─────────────┐
│ idle │ │ per line, A then B: │ │ lvl, lvl2 │ │ wra, wrb │ │ out, give │
│ the frame ├─>│ ┌─────────────┐ ┌────────────┐ ├─>│ u = x + fb y, ├─>│ both lines ├─>│ dry x + │
│ │ │ │ taps x 17: ├─>│ filt x 3: │ │ │ T tanh(u / T): │ │ written │ │ wet y │
│ │ │ │ the read │ │ hp, lp, g │ │ │ a table (514) │ │ │ │ pos += 1 │
│ │ │ └─────────────┘ └────────────┘ │ │ │ │ │ │ │
└───────────┘ └─────────────────────────────────┘ └────────────────┘ └────────────┘ └─────────────┘
tanh.l8).What it does. The model Reverb.hpp was
identified from the reference's impulse responses and reproduces them
sample by sample: a pre-delay, four diffusers, two early-reflection
loops, a late "ring" of four branches that feed each other, and a short
output kernel:
x0 = (l + r) / 2
s = diffuser x 4 (predelay(x0)) all-pass (g + z^-d) / (1 + g z^-d), d from Size
for c = A, B: two early loops: a line with an all-pass inside
w = eLine[c].read; eLine[c].write(allpass(s - fb * w))
e[c] = direct * s + gain * w
xin = 0.8503 * lateIn.read(1704) the late input, 1 704 samples later
for k = 0..3: the ring: branch k is fed by branch prev[k]
u = xin + y[prev[k]]
v1 = kLow u + (1 - kLow) highpass(u) the band split: the lows decay by kLow,
v2 = v1 + (kHigh - 1)(v1 - lowpass(v1)) the highs by kHigh (Low / High factor)
w = allpass(allpass(r * v2)) r: the decay per round (Time)
ring[k].write(w); lA += tap A of ring[k]; lB += tap B of ring[k]
A = we e[A] + wl lA, B = we e[B] + wl lB early / late mix
L, R = width matrix of A, B -> 15-tap kernel the reference's output smoothing
out = gd * x + gw * (L, R)
In the hardware the 22 delay lines -- pre-delay, four diffusers, two early all-passes, two early lines, the late input, eight late all-passes, four ring lines -- live in one block RAM, each exactly as long as its longest delay at Size 200 % (83 084 words of 18 bits, 54 of the chip's 135 block RAMs). Each line has its own write pointer that wraps at its length; a read is the pointer minus the delay, wrapped. The state machine reaches every line through the RAM's one read and one write port:
┌────────────┐ ┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌───────────────────────┐
│ idle │ │ pre, preq │ │ apr apq apw x 4 │ │ er erq ew ewp │ │ li liq: the late │
│ x0 ├─>│ the pre-delay├─>│ the diffusers ├─>│ x 2: the early ├─>│ input; rr x 4: the │
│ │ │ │ │ (the all-pass) │ │ loops │ │ ring lines' words │
└────────────┘ └──────────────┘ └──────────────────┘ └──────────────────┘ └───────────┬───────────┘
┌───────────────────────────────────────────────────────────────────────────────────────────┴───────────┐
│ per branch k = 0..3: lu (u) -> bq1 bq2 (high-pass) -> bv1 -> bq1 bq2 (low-pass) -> bv3 bv4 (r v2) │
│ -> apr apq apw x 2 (the late all-passes) -> lw lwp (the ring's write) -> ta tb tc (the two taps) │
└───────────────────────────────────────────────────┬───────────────────────────────────────────────────┘
┌──────────────────────────┐ ┌─────────────────────┴────┐ ┌───────────────────────────────────────────┐
│ give: the frame │<─┤ fir x 15, out: the │<─┤ mx mk: the early / late mix, the width, │
│ │ │ kernel, dry + wet │ │ into the kernel's line │
└──────────────────────────┘ └──────────────────────────┘ └───────────────────────────────────────────┘
apr read,
apq v = x - g d, apw write v, out = g v + d)
serves all fourteen all-passes -- diffusers, early and late -- with a
register saying whose it is.fx<2,16> (they peak at 1.6), the ring's
lines fx<4,14> (the ring builds up to 5.4 at Time
31.6 s; the first hardware reverb clipped there at 2 and its tail came
out weak). Between the lines the values are 32 bits, the coefficients 32
bits (two DSP48E1 a product). ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────────┐ ┌──────────────┐ ┌──────────────┐
│ chain output ├──>│ frame_mono: ├──>│ resample<1,6,180,60,24>: a ├──>│ q23_s16: ├──>│ mulaw: G.711 │
│ 48 kHz │ │ (l + r) / 2 │ │ 180-tap Kaiser FIR, every │ │ 16 bits │ │ 8 000 bytes/s│
│ stereo Q1.23 │ │ │ │ 6th frame: 8 kHz │ │ │ │ UART 115 200 │
└──────────────┘ └──────────────┘ └──────────────────────────────┘ └──────────────┘ └──────────────┘
dsp/algo/fir/resample.l8) is polyphase: only the one
output frame in six is computed, 180 taps at one multiplier, 30 clocks
of the 520. Designed for 60 dB stop band, 72 dB measured.linear2ulaw): 8 000 bytes a second is 69 % of the line, so
it fits beside the commands. It is for listening anywhere; the quality
check uses the Ethernet.The probe (audio/dsp/line/probe.l8)
pairs each frame going into the chain with the frame coming out of it --
the chain is a pipeline of fixed order, so the n-th out belongs to the
n-th in -- and probe_pack puts 120 pairs into a UDP
datagram for 192.168.77.2, port 36 528:
┌────────────┬──────────────────────────────────────────┬──────────────────────────────────────────┬─────┐
│ seq, 4 B │ pair 0: in l, in r, out l, out r: 12 B │ pair 1 │ ... │
│ LE │ 24-bit words, LE │ │ │
└────────────┴──────────────────────────────────────────┴──────────────────────────────────────────┴─────┘
1 444 bytes, 400 datagrams a second (4.6 Mb/s); a pair the network refuses is counted, never misaligned
The datagrams go to the PC that last sent a packet to the port (the
panel sends "hello"). The PC runs the C++ models on the in
frames and compares their output with the out frames: the
hardware is checked on the music, at any knob setting, while it
plays.
What each unit takes of the FPGA (an XC7A100T: 240 DSP48E1 slices,
135 block RAMs of 36 Kb, 63 400 LUTs), from the build of the bitstream:
the hierarchy's utilization after place and route, and synthesis's
DSP Final Report, which lists every DSP48E1 slice with
the formula it computes. A slice multiplies 25 x 18 bits (its A port is
30 bits wide, the multiplier takes 25 of them); a wider product takes
two slices, the second adding the first's upper half from the cascade
(PCIN>>17+A*B, its A then 0 bits when only B carries
the upper half). C+A*B and (P+A*B) use the
slice's post-adder; a ' marks a pipeline register synthesis
moved into the slice.
| unit | DSP48E1 slices | of them the upper half of a wide product (PCIN>>17) | with the post-adder (C+, P+) | block RAM | LUTs | FFs |
|---|---|---|---|---|---|---|
| compressor -- the compressor | 28 | 13 | 0 | -- | 2288 | 248 |
| chorus -- the chorus | 21 | 5 | 5 | 4 x 36 Kb + 1 x 18 Kb | 4383 | 345 |
| flanger -- the flanger | 31 | 13 | 1 | 7 x 36 Kb | 8180 | 2455 |
| delay -- the delay | 23 | 7 | 5 | 9 x 36 Kb | 1734 | 547 |
| reverb -- the reverb | 76 | 38 | 0 | 54 x 36 Kb | 5208 | 3102 |
| resampler -- 48 kHz to 8 kHz for the serial line | 1 | 0 | 0 | -- | 4957 | 4458 |
| Paula's mixer -- the four channels' volumes, the box filter to 48 kHz | 8 | 0 | 2 | -- | 1618 | 925 |
| 68000 -- MULU / MULS | 4 | 1 | 0 | -- | 6115 | 1046 |
| the whole design (the Ethernet and the UART too) | 192 of 240 | 106 x 36 Kb + 4 x 18 Kb of 135 | 37464 of 63 400 | 23409 |
Every slice, as synthesis's DSP Final Report lists it -- the formula the slice computes, its ports' widths in bits (A x B, the adder's C, the result P) and its pipeline registers (A2: two registers on A, and so on):
| # | formula | A | B | C | P | registers |
|---|---|---|---|---|---|---|
| 0 | A*B |
24 | 18 | - | 48 | -- |
| 1 | A*B |
24 | 17 | - | 48 | -- |
| 2 | PCIN>>17+A*B |
24 | 17 | - | 48 | -- |
| 3 | A*B |
20 | 17 | - | 48 | -- |
| 4 | PCIN>>17+A*B |
0 | 18 | - | 30 | -- |
| 5 | A*B' |
30 | 17 | - | 48 | B1 |
| 6 | PCIN>>17+A*B' |
0 | 18 | - | 48 | B1 |
| 7 | A''*B |
24 | 17 | - | 48 | A2 |
| 8 | PCIN>>17+A''*B |
24 | 18 | - | 32 | A2 |
| 9 | A''*B |
24 | 17 | - | 48 | A2 |
| 10 | PCIN>>17+A''*B |
24 | 18 | - | 32 | A2 |
| 11 | A*B |
17 | 18 | - | 48 | -- |
| 12 | PCIN>>17+A*B |
30 | 18 | - | 34 | -- |
| 13 | A*B |
17 | 17 | - | 48 | -- |
| 14 | PCIN>>17+A*B |
17 | 18 | - | 48 | -- |
| 15 | A*B |
30 | 17 | - | 48 | -- |
| 16 | PCIN>>17+A*B |
0 | 18 | - | 48 | -- |
| 17 | A*B'' |
24 | 17 | - | 48 | B2 |
| 18 | PCIN>>17+A*B'' |
24 | 18 | - | 36 | B2 |
| 19 | A'*B' |
30 | 17 | - | 48 | A1 B1 |
| 20 | PCIN>>17+A*B' |
0 | 18 | - | 29 | B1 |
| 21 | A'*B' |
17 | 17 | - | 48 | A1 B1 |
| 22 | PCIN>>17+A*B' |
0 | 18 | - | 46 | B1 |
| 23 | A'*B' |
30 | 17 | - | 48 | A1 B1 |
| 24 | PCIN>>17+A*B' |
0 | 18 | - | 29 | B1 |
| 25 | A'*B' |
17 | 17 | - | 48 | A1 B1 |
| 26 | PCIN>>17+A*B' |
0 | 18 | - | 46 | B1 |
| 27 | A*B' |
24 | 12 | - | 48 | B1 |
| # | formula | A | B | C | P | registers |
|---|---|---|---|---|---|---|
| 0 | A''*B |
23 | 17 | - | 48 | A2 |
| 1 | PCIN>>17+A''*B |
23 | 18 | - | 48 | A2 |
| 2 | A''*B |
17 | 17 | - | 48 | A2 |
| 3 | PCIN>>17+A''*B |
17 | 18 | - | 48 | A2 |
| 4 | A''*B |
23 | 17 | - | 48 | A2 |
| 5 | PCIN>>17+A''*B |
23 | 18 | - | 48 | A2 |
| 6 | A''*B |
17 | 17 | - | 48 | A2 |
| 7 | PCIN>>17+A''*B |
17 | 18 | - | 48 | A2 |
| 8 | A*B'' |
30 | 17 | - | 48 | B2 |
| 9 | PCIN>>17+A*B'' |
0 | 18 | - | 35 | B2 |
| 10 | C+A*B |
30 | 16 | 39 | 39 | -- |
| 11 | C+A*B |
30 | 16 | 40 | 40 | -- |
| 12 | C'+A*B |
30 | 16 | 39 | 39 | C1 |
| 13 | A'*B'' |
30 | 18 | - | 37 | A1 B2 |
| 14 | A'*B'' |
30 | 18 | - | 37 | A1 B2 |
| 15 | C+A'*B'' |
30 | 18 | 44 | 44 | A1 B2 |
| 16 | A'*B'' |
30 | 18 | - | 37 | A1 B2 |
| 17 | A'*B'' |
30 | 18 | - | 37 | A1 B2 |
| 18 | C+A'*B'' |
30 | 18 | 44 | 44 | A1 B2 |
| 19 | A*B |
30 | 17 | - | 48 | -- |
| 20 | A*B |
30 | 17 | - | 48 | -- |
| # | formula | A | B | C | P | registers |
|---|---|---|---|---|---|---|
| 0 | A*B |
30 | 17 | - | 48 | -- |
| 1 | PCIN>>17+A*B |
0 | 18 | - | 35 | -- |
| 2 | A*B |
30 | 7 | - | 48 | -- |
| 3 | A*B |
24 | 17 | - | 48 | -- |
| 4 | PCIN>>17+A*B |
30 | 17 | - | 48 | -- |
| 5 | A*B |
30 | 17 | - | 48 | -- |
| 6 | PCIN>>17+A*B |
30 | 18 | - | 48 | -- |
| 7 | A*B |
30 | 17 | - | 48 | -- |
| 8 | PCIN>>17+A*B |
30 | 18 | - | 48 | -- |
| 9 | C+A*B' |
30 | 12 | 39 | 39 | B1 |
| 10 | A'*B |
30 | 17 | - | 48 | A1 |
| 11 | PCIN>>17+A*B |
0 | 18 | - | 36 | -- |
| 12 | A*B |
30 | 17 | - | 48 | -- |
| 13 | PCIN>>17+A*B |
30 | 17 | - | 43 | -- |
| 14 | A*B |
17 | 17 | - | 48 | -- |
| 15 | PCIN>>17+A*B |
30 | 17 | - | 48 | -- |
| 16 | A*B |
24 | 17 | - | 48 | -- |
| 17 | PCIN>>17+A*B |
0 | 18 | - | 48 | -- |
| 18 | A*B |
30 | 17 | - | 48 | -- |
| 19 | PCIN>>17+A*B |
0 | 18 | - | 48 | -- |
| 20 | A'*B |
30 | 18 | - | 48 | A1 |
| 21 | A*B' |
30 | 17 | - | 48 | B1 |
| 22 | PCIN>>17+A'*B |
30 | 17 | - | 48 | A1 |
| 23 | A*B' |
30 | 17 | - | 48 | B1 |
| 24 | PCIN>>17+A*B' |
0 | 18 | - | 48 | B1 |
| 25 | A'*B |
30 | 18 | - | 48 | A1 |
| 26 | A*B' |
30 | 17 | - | 48 | B1 |
| 27 | PCIN>>17+A'*B |
30 | 17 | - | 48 | A1 |
| 28 | A'*B |
30 | 18 | - | 48 | A1 |
| 29 | A*B' |
30 | 17 | - | 48 | B1 |
| 30 | PCIN>>17+A'*B |
30 | 17 | - | 48 | A1 |
| # | formula | A | B | C | P | registers |
|---|---|---|---|---|---|---|
| 0 | A*B |
30 | 17 | - | 48 | -- |
| 1 | PCIN>>17+A*B |
0 | 18 | - | 34 | -- |
| 2 | (P+A*B)' |
30 | 18 | - | 40 | P1 |
| 3 | A*B |
30 | 17 | - | 48 | -- |
| 4 | PCIN>>17+A*B |
0 | 18 | - | 34 | -- |
| 5 | A*B |
30 | 17 | - | 48 | -- |
| 6 | PCIN>>17+A*B |
0 | 18 | - | 34 | -- |
| 7 | Dynamic |
- | - | - | 43 | -- |
| 8 | C+A'*B'' |
30 | 18 | 45 | 45 | A1 B2 |
| 9 | Dynamic |
- | - | - | 43 | -- |
| 10 | C+A'*B'' |
30 | 18 | 45 | 45 | A1 B2 |
| 11 | Dynamic |
- | - | - | 43 | -- |
| 12 | A''*B' |
30 | 17 | - | 48 | A2 B1 |
| 13 | PCIN>>17+A''*B' |
30 | 18 | - | 33 | A2 B1 |
| 14 | Dynamic |
- | - | - | 43 | -- |
| 15 | A''*B' |
30 | 17 | - | 48 | A2 B1 |
| 16 | PCIN>>17+A''*B' |
30 | 18 | - | 33 | A2 B1 |
| 17 | C+A*B |
30 | 11 | 28 | 32 | -- |
| 18 | PCIN+A:B+C |
0 | 1 | - | 33 | -- |
| 19 | Dynamic |
- | - | - | 42 | -- |
| 20 | C+A*B |
30 | 11 | 28 | 32 | -- |
| 21 | PCIN+A:B+C |
0 | 1 | - | 33 | -- |
| 22 | Dynamic |
- | - | - | 42 | -- |
| # | formula | A | B | C | P | registers |
|---|---|---|---|---|---|---|
| 0 | A*B |
17 | 18 | - | 48 | -- |
| 1 | PCIN>>17+A*B |
30 | 13 | - | 48 | -- |
| 2 | A'*B'' |
30 | 17 | - | 48 | A1 B2 |
| 3 | PCIN>>17+A'*B'' |
30 | 18 | - | 48 | A1 B2 |
| 4 | A*B |
17 | 18 | - | 48 | -- |
| 5 | PCIN>>17+A*B |
30 | 18 | - | 48 | -- |
| 6 | A*B |
17 | 17 | - | 48 | -- |
| 7 | PCIN>>17+A*B |
17 | 18 | - | 48 | -- |
| 8 | A*B |
30 | 17 | - | 48 | -- |
| 9 | PCIN>>17+A*B |
0 | 18 | - | 48 | -- |
| 10 | A*B |
30 | 17 | - | 48 | -- |
| 11 | PCIN>>17+A*B |
0 | 18 | - | 48 | -- |
| 12 | A*B |
30 | 17 | - | 48 | -- |
| 13 | PCIN>>17+A*B |
0 | 18 | - | 48 | -- |
| 14 | A*B |
30 | 17 | - | 48 | -- |
| 15 | PCIN>>17+A*B |
0 | 18 | - | 48 | -- |
| 16 | A*B |
30 | 17 | - | 48 | -- |
| 17 | PCIN>>17+A*B |
0 | 18 | - | 48 | -- |
| 18 | A*B'' |
30 | 17 | - | 48 | B2 |
| 19 | PCIN>>17+A*B'' |
0 | 18 | - | 48 | B2 |
| 20 | A*B'' |
30 | 17 | - | 48 | B2 |
| 21 | PCIN>>17+A*B'' |
0 | 18 | - | 48 | B2 |
| 22 | A*B'' |
30 | 17 | - | 48 | B2 |
| 23 | PCIN>>17+A*B'' |
0 | 18 | - | 48 | B2 |
| 24 | A*B'' |
30 | 17 | - | 48 | B2 |
| 25 | PCIN>>17+A*B'' |
0 | 18 | - | 40 | B2 |
| 26 | A*B'' |
30 | 17 | - | 48 | B2 |
| 27 | PCIN>>17+A*B'' |
0 | 18 | - | 48 | B2 |
| 28 | A*B |
30 | 17 | - | 48 | -- |
| 29 | PCIN>>17+A*B |
0 | 18 | - | 48 | -- |
| 30 | A*B'' |
30 | 17 | - | 48 | B2 |
| 31 | PCIN>>17+A*B'' |
0 | 18 | - | 48 | B2 |
| 32 | A*B'' |
17 | 18 | - | 48 | B2 |
| 33 | PCIN>>17+A''*B |
30 | 18 | - | 48 | A2 |
| 34 | A''*B |
17 | 17 | - | 48 | A2 |
| 35 | PCIN>>17+A''*B |
17 | 18 | - | 48 | A2 |
| 36 | A*B'' |
30 | 17 | - | 48 | B2 |
| 37 | PCIN>>17+A*B'' |
0 | 18 | - | 48 | B2 |
| 38 | A*B'' |
17 | 18 | - | 48 | B2 |
| 39 | PCIN>>17+A''*B |
30 | 18 | - | 48 | A2 |
| 40 | A''*B |
17 | 17 | - | 48 | A2 |
| 41 | PCIN>>17+A''*B |
17 | 18 | - | 48 | A2 |
| 42 | A*B'' |
17 | 18 | - | 48 | B2 |
| 43 | PCIN>>17+A''*B |
30 | 18 | - | 48 | A2 |
| 44 | A''*B |
17 | 17 | - | 48 | A2 |
| 45 | PCIN>>17+A''*B |
17 | 18 | - | 48 | A2 |
| 46 | A*B'' |
30 | 17 | - | 48 | B2 |
| 47 | PCIN>>17+A*B'' |
0 | 18 | - | 48 | B2 |
| 48 | A*B'' |
17 | 18 | - | 48 | B2 |
| 49 | PCIN>>17+A''*B |
30 | 18 | - | 48 | A2 |
| 50 | A''*B |
17 | 17 | - | 48 | A2 |
| 51 | PCIN>>17+A''*B |
17 | 18 | - | 48 | A2 |
| 52 | A*B |
30 | 17 | - | 48 | -- |
| 53 | PCIN>>17+A*B |
0 | 18 | - | 48 | -- |
| 54 | A*B'' |
17 | 18 | - | 48 | B2 |
| 55 | PCIN>>17+A''*B |
30 | 18 | - | 48 | A2 |
| 56 | A''*B |
17 | 17 | - | 48 | A2 |
| 57 | PCIN>>17+A''*B |
17 | 18 | - | 48 | A2 |
| 58 | A'*B'' |
30 | 17 | - | 48 | A1 B2 |
| 59 | PCIN>>17+A'*B'' |
30 | 18 | - | 48 | A1 B2 |
| 60 | A*B'' |
17 | 18 | - | 48 | B2 |
| 61 | PCIN>>17+A''*B |
30 | 18 | - | 48 | A2 |
| 62 | A''*B |
17 | 17 | - | 48 | A2 |
| 63 | PCIN>>17+A''*B |
17 | 18 | - | 48 | A2 |
| 64 | A*B'' |
17 | 18 | - | 48 | B2 |
| 65 | PCIN>>17+A''*B |
30 | 18 | - | 48 | A2 |
| 66 | A''*B |
17 | 17 | - | 48 | A2 |
| 67 | PCIN>>17+A''*B |
17 | 18 | - | 48 | A2 |
| 68 | A*B |
30 | 17 | - | 48 | -- |
| 69 | PCIN>>17+A*B |
0 | 18 | - | 48 | -- |
| 70 | A*B'' |
17 | 18 | - | 48 | B2 |
| 71 | PCIN>>17+A''*B |
30 | 18 | - | 48 | A2 |
| 72 | A''*B |
17 | 17 | - | 48 | A2 |
| 73 | PCIN>>17+A''*B |
17 | 18 | - | 48 | A2 |
| 74 | A'*B'' |
30 | 17 | - | 48 | A1 B2 |
| 75 | PCIN>>17+A'*B'' |
30 | 18 | - | 48 | A1 B2 |
| # | formula | A | B | C | P | registers |
|---|---|---|---|---|---|---|
| 0 | A*B |
30 | 17 | - | 48 | -- |
| # | formula | A | B | C | P | registers |
|---|---|---|---|---|---|---|
| 0 | A*B |
16 | 6 | - | 22 | -- |
| 1 | C+A*B |
16 | 6 | 16 | 16 | -- |
| 2 | A*B |
30 | 18 | - | 39 | -- |
| 3 | A*B |
30 | 18 | - | 39 | -- |
| 4 | A*B |
16 | 6 | - | 22 | -- |
| 5 | C+A*B |
16 | 6 | 16 | 16 | -- |
| 6 | A*B |
30 | 18 | - | 39 | -- |
| 7 | A*B |
30 | 18 | - | 39 | -- |
| # | formula | A | B | C | P | registers |
|---|---|---|---|---|---|---|
| 0 | A'*B' |
16 | 16 | - | 32 | A1 B1 |
| 1 | A'*B' |
17 | 15 | - | 48 | A1 B1 |
| 2 | A'*B' |
17 | 17 | - | 48 | A1 B1 |
| 3 | PCIN>>17+A'*B' |
17 | 15 | - | 48 | A1 B1 |
The reverb takes the most: its coefficients are 32 bits so that a Time of 31.6 s still decays as the model's, and 32 x 25 bits is two DSP48E1. The block RAM is mostly lines: the reverb's 83 K words (54 tiles), the delay's 16 K (9), the flanger's 8 K (7), the chorus's 8 K (4.5); the 68000's memory is 32 tiles.
make check-effects), the song through each effect at the
panel's defaults, board against model after 8 s:| effect | board vs model |
|---|---|
| compressor | -97.8 dB |
| chorus | -65 .. -73 dB (its LFO phase aligned to the board's) |
| flanger | -116.3 dB (LFO aligned; the model's output clipped to full scale, as the hardware's) |
| delay | -90.7 dB |
| reverb | -78.9 / -80.4 dB (left / right) |
| all five in a row | -71.4 dB |
An LFO only runs while its unit gets frames, so the board's phase
depends on when it was switched on; the check searches the phase that
fits (host/probe_phase.py). Where the model's output goes
past full scale (the delay and the flanger at high feedback) the
hardware saturates and the model does not: those samples are compared
clipped.
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ the 68000 68k.l8, m68k_alu.l8 │
│ ┌──────────────────────────────┐ ┌────────────────────────────────────────────────────────┐ │
│ │ M68State: the state struct │ │ decode, combinational, from the instruction word │ │
│ │ D0-D7, A0-A6, USP, SSP │ │ bitslices M68W, M68Move -> m68_decode: the kind │ │
│ │ SR (T S I2-I0 X N Z V C), PC │ │ m68_plan: source, destination, sizes, ALU operation │ │
│ └──────────────────────────────┘ └────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ sm { }: one state machine, the steps every instruction is made of │ │
│ │ │ │
│ │ rst0..3 -> fetch -> disp -> [src EA -> rd] -> [dst EA -> rd] -> exe -> [wr] -> done │ │
│ │ ^ an interrupt above the mask: exc0..5, the autovector 24 + level │ │
│ │ │ │
│ │ the EA walk ea_d16 ea_idx ea_absw ea_absl ea_imm: the extension words, then earet │ │
│ │ the branches bcc dbcc, jmp jsr rts rtr rte, link unlk, psh / pop │ │
│ │ the loops shf: a shift bit a clock, dv: a divide bit a clock, mvm: MOVEM, mvp: MOVEP │ │
│ │ exceptions TRAP CHK zero divide illegal privilege line A / F: exc0..5, six bytes │ │
│ └──────────────────────────────────────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────┐ ┌──────────────────────────────────────────┐ │
│ │ m68_alu: one function, the result and the │ │ the bus: one access at a time │ │
│ │ flags; add sub, logic, BCD, MUL, bit ops │ │ AS R/W UDS LDS A23-A0 D15-D0, DTACK (ack)│ │
│ └────────────────────────────────────────────┘ └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
The whole 68000 instruction set, the exceptions and the seven
interrupt levels with autovectors. Not yet: the address error and the
trace exception (Musashi, the reference, takes neither), and the
pipe {} its design rules ask for -- the 68000's own
two-word prefetch is where it goes.
boot.s is everything the system runs besides ptplayer:
the vectors, a start that hands the module to ptplayer, and an idle
loop. Assembled with vasm together with ptplayer and the module (src/boot.s):
; boot.s -- the soundtracker system's whole program: the 68000's vectors, a start that hands the module to ptplayer,
; and an idle loop. Everything else happens in ptplayer's level-6 interrupt (CIA-B timer A: mt_music at the
; song's tempo; timer B: audio DMA on, then the loop pointers).
;
; The memory is ONE block RAM whose initial contents are this image (`make image`): vectors at 0, this code,
; ptplayer with its variables (it writes them pc-relative, so it must sit in writable memory), the module, and the
; stack at the top. Assembled with vasm (Motorola syntax), linked at 0, `-Fbin`.
;
; 0x000000 the block RAM (MEMSIZE bytes): vectors, code, ptplayer, the module; the stack grows down from the top
; 0xBFD000 CIA-B (registers on the even byte, a register every 0x100)
; 0xDFF000 the custom chips: INTENA/INTREQ/DMACON and Paula's four audio channels
MEMSIZE equ $20000
MINIMAL equ 1 ; the player alone: no sound effects, no master volume
ENABLE_SAWRECT equ 1
org 0
vectors:
dc.l MEMSIZE ; 0: the initial SSP
dc.l start ; 1: the reset pc
rept 22 ; 2..23: bus/address error, illegal, zero divide, CHK, TRAPV, privilege, trace,
dc.l fault ; line A/F, reserved, spurious (24) excluded
endr
dc.l fault ; 24: spurious interrupt
rept 7 ; 25..31: the autovectors, levels 1..7; ptplayer sets level 6 ($78) itself
dc.l fault
endr
rept 32 ; 32..63: TRAP #0..15 and the reserved ones
dc.l fault
endr
start:
lea $dff000,a6
move.w #$7fff,$9a(a6) ; INTENA: every source off
move.w #$7fff,$9c(a6) ; INTREQ: every request cleared
move.w #$7fff,$96(a6) ; DMACON: every channel off
move.w #$8200,$96(a6) ; DMACON: DMAEN (the master enable); ptplayer turns the channels on
sub.l a0,a0 ; the vector base: 0 on a 68000
moveq #1,d0 ; PAL timer clock
bsr _mt_install
lea module,a0
sub.l a1,a1 ; the samples follow the patterns
moveq #0,d0 ; from song position 0
bsr _mt_init
st _mt_Enable
move.w #$2000,sr ; supervisor, interrupt mask 0
idle: bra.s idle
fault: move.w #$2700,sr ; anything unexpected: stop with every interrupt masked
move.w #$000f,$dff096 ; silence
.h: bra.s .h
cnop 0,4
module:
include "module.i" ; `incbin` of the module (written by the Makefile)
cnop 0,4
; LAST: ptplayer.asm ends with `end`, which stops the assembly
include "ptplayer.asm"ptplayer 6.4 -- "Protracker V2.3B Playroutine" by Frank
Wille, public domain, built with MINIMAL=1: src/ptplayer.asm (4 000 lines; the whole
source below).
;**************************************************
;* ----- Protracker V2.3B Playroutine ----- *
;**************************************************
;
; Version 6.4
; Written by Frank Wille in 2013, 2016-2024.
;
; I, the copyright holder of this work, hereby release it into the
; public domain. This applies worldwide.
;
; The default version (single section, local base register) should
; work with most assemblers. Tested are: Devpac, vasm, PhxAss,
; Barfly-Asm, SNMA, AsmOne, AsmPro.
;
; The small data model can be enabled by defining the symbol SDATA. In
; this case all functions expect a4 to be initialized with the small
; data base register. Interrupt functions restore a4 from _LinkerDB.
; Small data may work with vasm and PhxAss only.
;
; Exported functions and variables:
; (CUSTOM is the custom-chip register set base address $dff000.)
;
; _mt_install(a6=CUSTOM, a0=VectorBase, d0=PALflag.b) [OSCOMPAT=0]
; Install a CIA-B interrupt for calling mt_music or mt_sfxonly.
; The music module is replayed via mt_music when _mt_Enable is non-zero.
; Otherwise the interrupt handler calls mt_sfxonly to play sound
; effects only. VectorBase is 0 for 68000, otherwise set it to the CPU's
; VBR register. A non-zero PALflag selects PAL-clock for the CIA timers
; (NTSC otherwise).
;
; ok(d0) = _mt_install() [OSCOMPAT=1]
; Register CIA-B interrupts with AmigaOS for calling mt_music or
; mt_sfxonly. Allocate all Paula audio channels via audio.device.
; The music module is replayed via mt_music when _mt_Enable is non-zero.
; Otherwise the interrupt handler calls mt_sfxonly to play sound
; effects only.
; Returns true (1) on success, false (0) otherwise. You must not call
; _mt_remove(), when _mt_install() failed!
;
; _mt_remove(a6=CUSTOM) [OSCOMPAT=0]
; Remove the CIA-B music interrupt, restore the previous handler and
; reset the CIA timer registers to their original values.
;
; _mt_remove() [OSCOMPAT=1]
; Unregister the CIA-B interrupts handlers and deallocate all
; audio channels.
;
; _mt_init(a6=CUSTOM, a0=TrackerModule, a1=Samples|NULL, d0=InitialSongPos.b)
; Initialize a new module.
; Reset speed to 6, tempo to 125 and start at the given song position.
; Master volume is at 64 (maximum).
; When a1 is NULL the samples are assumed to be stored after the patterns.
;
; _mt_end(a6=CUSTOM)
; Stop playing current module and sound effects.
;
; _mt_soundfx(a6=CUSTOM, a0=SamplePointer,
; d0=SampleLength.w, d1=SamplePeriod.w, d2=SampleVolume.w)
; Request playing of an external sound effect on the most unused channel.
; This function is for compatibility with the old API only.
; You may prefer _mt_playfx instead. MINIMAL=0 only.
;
; channelStatus(d0) = _mt_playfx(a6=CUSTOM, a0=SfxStructurePointer)
; Request playing of a prioritized external sound effect, either on a
; fixed channel or on the most unused one.
; Structure layout of SfxStructure:
; void *sfx_ptr (pointer to sample start in Chip RAM, even address)
; WORD sfx_len (sample length in words)
; WORD sfx_per (hardware replay period for sample)
; WORD sfx_vol (volume 0..64, is unaffected by the song's master volume)
; BYTE sfx_cha (0..3 selected replay channel, -1 selects best channel)
; BYTE sfx_pri (priority, must be in the range 1..127)
; When multiple samples are assigned to the same channel the lower
; priority sample will be replaced. When priorities are the same, then
; the older sample is replaced.
; The chosen channel is blocked for music until the effect has
; completely been replayed.
; Returns a pointer to a channel-status structure when the sample
; is scheduled for playing, and NULL when the request was ignored.
; MINIMAL=0 only.
;
; _mt_loopfx(a6=CUSTOM, a0=SfxStructurePointer)
; Request playing of a looped sound effect on a fixed channel, which
; will be blocked for music until the effect is stopped (_mt_stopfx).
; It uses the same sfx-structure as _mt_playfx, but the priority is
; ignored. A looped sound effect has always highest priority and will
; replace a previous effect on the same channel. No automatic channel
; selection possible!
; Also make sure the sample starts with a zero-word, which is used
; for idling when the effect is stopped. This word is included in the
; total length calculation, but excluded when actually playing the loop.
; MINIMAL=0 only.
;
; _mt_stopfx(a6=CUSTOM, d0=Channel.b) [MINIMAL=0]
; Immediately stop a currently playing sound effect on a channel (0..3)
; and make it available for music, or other effects, again. This is the
; only way to stop a looped sound effect (_mt_loopfx) besides stopping
; replay completely (_mt_end).
;
; _mt_musicmask(a6=CUSTOM, d0=ChannelMask.b) [MINIMAL=0]
; Bits set in the ChannelMask define which specific channels are reserved
; for music only. Set bit 0 for channel 0, ..., bit 3 for channel 3.
; When calling _mt_soundfx or _mt_playfx with automatic channel selection
; (sfx_cha=-1) then these masked channels will never be picked.
; The mask defaults to 0.
;
; _mt_mastervol(a6=CUSTOM, d0=MasterVolume.w) [MINIMAL=0]
; Set a master volume from 0 to 64 for all music channels.
; Note that the master volume does not affect the volume of external
; sound effects (which is desired).
;
; _mt_samplevol(d0=SampleNumber.w, d1=Volume.b) [MINIMAL=0]
; Redefine a sample's volume. May also be done while the song is playing.
; Warning: Does not check arguments for valid range! You must have done
; _mt_init before calling this function!
; The new volume is persistent. Even when the song is restarted.
;
; _mt_channelmask(a6=CUSTOM, d0=ChannelMask.b) [MINIMAL=0]
; Bits cleared in the ChannelMask define which specific channels are muted
; for music replay. Clear bit 0 for channel 0, ..., bit 3 for channel 3.
; Additionally a cleared bit prevents any access to the sample pointers
; of this channel.
; The mask defaults to all channels unmuted (bits set) and is reset to
; this state on _mt_init and _mt_end.
;
; _mt_music(a6=CUSTOM)
; The replayer routine. Can be called from your own VBlank interrupt
; handler when VBLANK_MUSIC is set. Is otherwise called automatically
; by Timer-A interrupts after _mt_install.
;
; _mt_dmaon() [NO_TIMERS=1]
; MUST be called ca. 550 ticks after calling _mt_music. Enables Audio
; DMA to play a new note.
;
; _mt_setrep() [NO_TIMERS=1]
; MUST be called ca. 550 ticks after calling _mt_dmaon. Sets the
; repetition pointers and lengths for looped samples.
;
; Byte Variables:
;
; _mt_Enable [VBLANK_MUSIC=0]
; Set this byte to non-zero to play music, zero to pause playing.
; Note that you can still play sound effects, while music is stopped.
; It is set to 0 by _mt_install.
;
; _mt_E8Trigger
; This byte reflects the value of the last E8 command.
; It is reset to 0 after _mt_init.
;
; _mt_MusicChannels [MINIMAL=0]
; This byte defines the number of channels which should be dedicated
; for playing music. So sound effects will never use more
; than 4 - _mt_MusicChannels channels at once. Defaults to 0.
;
; Set this if you want to build a version which uses AmigaOS to
; register interrupts and reserve audio channels.
ifnd OSCOMPAT
OSCOMPAT equ 0
endc
; Optionally you can build a minimal version, which includes only
; the basic player. No sound effects insertion, no master volume, no
; sample volume, etc. Define the symbol MINIMAL=1 for it.
ifnd MINIMAL
MINIMAL equ 0
endc
; You may disable sawtooth and rectangle vibratos/tremolos here, which
; will be replaced by sine-waves. They are rarely used and disabling
; them will free a lot of memory for the tables.
ifnd ENABLE_SAWRECT
ENABLE_SAWRECT equ 1
endc
; Set this if you can guarantee that the word at $0 is cleared, and if
; you want to use it for idle-looping of samples.
ifnd NULL_IS_CLEARED
NULL_IS_CLEARED equ 0
endc
; Setting NO_TIMERS disables the use of both CIA-B timers completely, which
; means that the player doesn't depend on level-6 EXTER interrupts anymore.
; NO_TIMERS=1 automatically includes VBLANK_MUSIC=1 (see below).
; With NO_TIMERS set, your main program has to call all music subroutines
; itself. The typical procedure in a VERTB-based main loop would be:
; 1. call _mt_music
; 2. wait at least 550 ticks, then call _mt_dmaon
; 3. wait at least 550 ticks again, then call _mt_setrep
; The last two steps could for example be performed by Copper interrupts.
ifnd NO_TIMERS
NO_TIMERS equ 0
else
ifne NO_TIMERS
ifd VBLANK_MUSIC
ifeq VBLANK_MUSIC
fail "NO_TIMERS=1 requires VBLANK_MUSIC=1"
endc
endc ; VBLANK_MUSIC
endc ; NO_TIMERS=1
endc ; NO_TIMERS
; This disables only initialization of CIA Timer-A interrupts for music
; replay, which means you cannot set the tempo with the F-command
; anymore and you have to call _mt_music yourself out of your own
; VBlank interrupt handler. Also sound effects will no longer work,
; when no music is playing (_mt_Enable=0).
ifnd VBLANK_MUSIC
VBLANK_MUSIC equ NO_TIMERS
endc
; Delay in CIA-ticks, which guarantees that at least one Audio-DMA
; took place, even with the lowest periods.
; 496 should be the correct value. But there are some A1200 which
; need at least 550.
DMADELAY equ 576 ; was 496
; exec.library (OSCOMPAT)
_LVOAllocSignal equ -330
_LVOFreeSignal equ -336
_LVOOpenDevice equ -444
_LVOCloseDevice equ -450
_LVOOpenResource equ -498
; cia.resource (OSCOMPAT)
_LVOAddICRVector equ -6
_LVORemICRVector equ -12
_LVOAbleICR equ -18
CIAICRB_TA equ 0
CIAICRB_TB equ 1
; Custom chip registers
CUSTOM equ $dff000
INTREQR equ $01e
INTENAR equ $01c
DMACON equ $096
INTENA equ $09a
INTREQ equ $09c
AUD0LC equ $0a0
AUD0LEN equ $0a4
AUD0VOL equ $0a8
AUD1LC equ $0b0
AUD1LEN equ $0b4
AUD1VOL equ $0b8
AUD2LC equ $0c0
AUD2LEN equ $0c4
AUD2VOL equ $0c8
AUD3LC equ $0d0
AUD3LEN equ $0d4
AUD3VOL equ $0d8
; Audio channel registers
AUDLC equ 0
AUDLEN equ 4
AUDPER equ 6
AUDVOL equ 8
; CIA registers
CIAA equ $bfe001
CIAB equ $bfd000
CIAPRA equ $000
CIATALO equ $400
CIATAHI equ $500
CIATBLO equ $600
CIATBHI equ $700
CIAICR equ $d00
CIACRA equ $e00
CIACRB equ $f00
; Sound effects structure, passed into _mt_playfx
rsreset
sfx_ptr rs.l 1
sfx_len rs.w 1
sfx_per rs.w 1
sfx_vol rs.w 1
sfx_cha rs.b 1
sfx_pri rs.b 1
sfx_sizeof rs.b 0
; Channel Status
rsreset
n_note rs.w 1
n_cmd rs.b 1
n_cmdlo rs.b 1
n_index rs.b 1
n_sfxpri rs.b 1
n_reserved1 rs.b 2
n_start rs.l 1
n_loopstart rs.l 1
n_length rs.w 1
n_replen rs.w 1
n_period rs.w 1
n_volume rs.w 1
n_pertab rs.l 1
n_dmabit rs.w 1
n_noteoff rs.w 1
n_toneportspeed rs.w 1
n_wantedperiod rs.w 1
n_pattpos rs.w 1
n_funk rs.w 1
n_wavestart rs.l 1
n_reallength rs.w 1
n_intbit rs.w 1
n_sfxptr rs.l 1
n_sfxlen rs.w 1
n_sfxper rs.w 1
n_sfxvol rs.w 1
n_looped rs.b 1
n_minusft rs.b 1
n_vibratoamp rs.b 1
n_vibratospd rs.b 1
n_vibratopos rs.b 1
n_vibratoctrl rs.b 1
n_tremoloamp rs.b 1
n_tremolospd rs.b 1
n_tremolopos rs.b 1
n_tremoloctrl rs.b 1
n_gliss rs.b 1
n_sampleoffset rs.b 1
n_loopcount rs.b 1
n_funkoffset rs.b 1
n_retrigcount rs.b 1
ifeq MINIMAL
n_freecnt rs.b 1
n_musiconly rs.b 1
n_enable rs.b 1
else
n_reserved2 rs.b 3
endc
n_sizeof rs.b 0
DISABLE macro
ifeq NO_TIMERS
move.w #$4000,INTENA(a6)
endc
endm
ENABLE macro
ifeq NO_TIMERS
move.w #$c000,INTENA(a6)
endc
endm
ifd SDATA
xref _LinkerDB ; small data base from linker
near a4
code
endc
ifne OSCOMPAT
;---------------------------------------------------------------------------
xdef _mt_install
_mt_install:
; Register a CIA-B interrupt with AmigaOS for calling mt_music.
; Reserve all audio channels for our use.
; -> d0.l = status (1 for success, 0 for error)
move.l a6,-(sp)
ifnd SDATA
move.l a4,-(sp)
lea mt_data(pc),a4
endc
; open audio.device and allocate all four audio channels
move.l 4.w,a6
moveq #-1,d0
jsr _LVOAllocSignal(a6)
lea mt_ioport+20(pc),a0
move.b d0,-5(a0) ; MP_SIGBIT
bmi .fail
move.l a0,8(a0) ; NewList MP_MSGLIST
addq.l #4,a0
clr.l (a0)
move.l a0,-(a0)
move.l 276(a6),-(a0) ; MP_SIGTASK = ThisTask
lea mt_audio_name(pc),a0
lea mt_ioaudio(pc),a1
moveq #0,d0
moveq #0,d1
jsr _LVOOpenDevice(a6)
tst.b d0
bne .no_device
ifeq NO_TIMERS
; attempt to allocate CIA-B timers A and B
lea mt_ciab_name(pc),a1
jsr _LVOOpenResource(a6)
tst.l d0
beq .no_rsrc
move.l d0,a6
clr.b mt_Enable(a4)
lea TB_toggle(pc),a0
clr.b (a0)
ifeq VBLANK_MUSIC
moveq #CIAICRB_TA,d0
lea mt_extern_timer_a_is(pc),a1
jsr _LVOAddICRVector(a6)
tst.l d0
bne .no_timer_a
endc ; !VBLANK_MUSIC
moveq #CIAICRB_TB,d0
lea mt_extern_timer_b_is(pc),a1
jsr _LVOAddICRVector(a6)
tst.l d0
bne .no_timer_b
; TimerA and TimerB interrupt disable
ifne VBLANK_MUSIC
moveq #$2,d0 ; TimerB only for VBLANK_MUSIC
else
moveq #$3,d0
endc
jsr _LVOAbleICR(a6)
lea CIAB,a0
; Stop Timer B
moveq #0,d0
move.b d0,CIACRB(a0)
ifeq VBLANK_MUSIC
; Stop Timer A
move.b d0,CIACRA(a0)
; determine if 02 clock for timers is based on PAL or NTSC
move.l 4.w,a1
cmp.b #50,531(a1) ; PowerSupplyFrequency
beq .1
move.l #1789773,d0 ; NTSC
bra .2
.1: move.l #1773447,d0 ; PAL
.2: move.l d0,mt_timerval(a4)
; load TimerA in continuous mode for the default tempo of 125
divu #125,d0
move.b d0,CIATALO(a0)
lsr.w #8,d0
move.b d0,CIATAHI(a0)
move.b #$11,CIACRA(a0) ; load timer, start continuous
endc ; !VBLANK_MUSIC
; load TimerB with DMADELAY ticks for setting DMA and repeat
move.b #DMADELAY&255,CIATBLO(a0)
move.b #DMADELAY>>8,CIATBHI(a0)
; TimerA and TimerB interrupt enable
ifne VBLANK_MUSIC
move.w #$82,d0 ; TimerB only for VBLANK_MUSIC
else
move.w #$83,d0
endc
jsr _LVOAbleICR(a6)
endc ; !NO_TIMERS
; reset the player
pea .popout(pc)
ifnd SDATA
move.l a4,-(sp)
endc
lea CUSTOM,a6
bra mt_reset
ifeq NO_TIMERS
.no_timer_b:
ifeq VBLANK_MUSIC
moveq #CIAICRB_TA,d0
lea mt_extern_timer_a_is(pc),a1
jsr _LVORemICRVector(a6)
.no_timer_a:
endc ; !VBLANK_MUSIC
endc ; !NO_TIMERS
move.l 4.w,a6
.no_rsrc:
lea mt_ioaudio(pc),a1
jsr _LVOCloseDevice(a6)
.no_device:
moveq #0,d0
move.b mt_ioport+15(pc),d0
jsr _LVOFreeSignal(a6)
.fail:
moveq #0,d0 ; Indicate failure
bra .out
.popout:
moveq #1,d0 ; Indicate success
.out:
ifnd SDATA
move.l (sp)+,a4
endc
move.l (sp)+,a6
rts
mt_ioport:
dc.l 0,0
dc.b 4,0 ; MT_MSGPORT
dc.l 0
dc.b 0,-1 ; PA_SIGNAL
dc.l 0
.head: dc.l .tail
.tail: dc.l 0
dc.l .head
mt_ioaudio:
dc.l 0,0
dc.b 7,127 ; NT_REPLYMSG, ADALLOC_MAXPREC
dc.l 0,mt_ioport
dc.w .end-mt_ioaudio
dc.l 0,0
dc.w $20 ; ADCMD_ALLOCATE
dc.b $40,0 ; ADIOF_NOWAIT
dc.w 0 ; ioa_AllocKey
dc.l mt_ch_alloc ; ioa_Data
dc.l 1 ; ioa_Length
dc.w 0,0,0
ds.b 20 ; ioa_WriteMsg
.end:
ifeq NO_TIMERS
ifeq VBLANK_MUSIC
mt_extern_timer_a_is:
dc.l 0,0
dc.b 2 ; NT_INTERRUPT
dc.b 0 ; priority
dc.l mt_timer_int_name
dc.l 0
dc.l mt_cia_timer_a_code
endc ; !VBLANK_MUSIC
mt_extern_timer_b_is:
dc.l 0,0
dc.b 2 ; NT_INTERRUPT
dc.b 0 ; priority
dc.l mt_timer_int_name
dc.l 0
dc.l mt_cia_timer_b_code
mt_ciab_name:
dc.b "ciab.resource",0
mt_timer_int_name:
dc.b "ptplayer timer",0
endc ; !NO_TIMERS
mt_audio_name:
dc.b "audio.device",0
mt_ch_alloc:
dc.b %1111 ; allocate all four channels
even
;---------------------------------------------------------------------------
xdef _mt_remove
_mt_remove:
; Unregister our CIA-B interrupt with AmigaOS. Free all audio channels.
move.l a6,-(sp)
ifeq NO_TIMERS
move.l 4.w,a6
lea mt_ciab_name(pc),a1
jsr _LVOOpenResource(a6)
tst.l d0
beq .nociares
move.l d0,a6
; TimerA and TimerB interrupt disable
ifne VBLANK_MUSIC
moveq #$02,d0 ; TimerB only for VBLANK_MUSIC
else
moveq #$03,d0
endc
jsr _LVOAbleICR(a6)
moveq #CIAICRB_TB,d0
lea mt_extern_timer_b_is(pc),a1
jsr _LVORemICRVector(a6)
ifeq VBLANK_MUSIC
moveq #CIAICRB_TA,d0
lea mt_extern_timer_a_is(pc),a1
jsr _LVORemICRVector(a6)
endc ; !VBLANK_MUSIC
.nociares:
endc ; !NO_TIMERS
; close audio.device, free channels
move.l 4.w,a6
lea mt_ioaudio(pc),a1
jsr _LVOCloseDevice(a6)
moveq #0,d0
move.b mt_ioport+15(pc),d0
jsr _LVOFreeSignal(a6)
move.l (sp)+,a6
rts
ifeq NO_TIMERS
ifeq VBLANK_MUSIC
;---------------------------------------------------------------------------
mt_cia_timer_a_code:
; TimerA interrupt calls mt_music at a selectable tempo (Fxx command),
; which defaults to 50 times per second.
movem.l d2-d7/a2-a6,-(sp)
lea CUSTOM,a6
ifd SDATA
lea _LinkerDB,a4
else
lea mt_data(pc),a4
endc
; do music when enabled
tst.b mt_Enable(a4)
ifeq MINIMAL
beq .2
else
beq .1
endc
bsr mt_music ; music with sfx inserted
.1: movem.l (sp)+,d2-d7/a2-a6
rts
ifeq MINIMAL
.2: bsr mt_sfxonly ; no music, only sfx
movem.l (sp)+,d2-d7/a2-a6
rts
endc
endc ; !VBLANK_MUSIC
;---------------------------------------------------------------------------
mt_cia_timer_b_code:
; Handle one-shot TimerB interrupt.
; TB_toggle-technique suggested by Ross/EAB.
lea TB_toggle(pc),a0
not.b (a0)
lea CUSTOM+INTREQ,a0
beq mt_TimerBsetrep
; restart timer for repeat, enable audio DMA after DMADELAY ticks
move.b #$19,CIAB+CIACRB
move.w mt_dmaon(pc),DMACON-INTREQ(a0)
rts
TB_toggle:
dc.b 0
even
mt_TimerBsetrep:
; Oneshot TimerB interrupt to set repeat samples after another DMADELAY ticks.
move.l a4,-(sp)
ifd SDATA
lea _LinkerDB,a4
else
lea mt_data(pc),a4
endc
; clear possible audio interrupt flags
moveq #0,d0
or.b mt_dmaon+1(pc),d0
lsl.w #7,d0
move.w d0,(a0)
; set repeat sample pointers and lengths
ifne MINIMAL
move.l mt_chan1+n_loopstart(a4),AUD0LC-INTREQ(a0)
move.w mt_chan1+n_replen(a4),AUD0LEN-INTREQ(a0)
move.l mt_chan2+n_loopstart(a4),AUD1LC-INTREQ(a0)
move.w mt_chan2+n_replen(a4),AUD1LEN-INTREQ(a0)
move.l mt_chan3+n_loopstart(a4),AUD2LC-INTREQ(a0)
move.w mt_chan3+n_replen(a4),AUD2LEN-INTREQ(a0)
move.l mt_chan4+n_loopstart(a4),AUD3LC-INTREQ(a0)
move.w mt_chan4+n_replen(a4),AUD3LEN-INTREQ(a0)
else ; !MINIMAL
tst.b mt_chan1+n_enable(a4)
beq .1
move.l mt_chan1+n_loopstart(a4),AUD0LC-INTREQ(a0)
move.w mt_chan1+n_replen(a4),AUD0LEN-INTREQ(a0)
.1: tst.b mt_chan2+n_enable(a4)
beq .2
move.l mt_chan2+n_loopstart(a4),AUD1LC-INTREQ(a0)
move.w mt_chan2+n_replen(a4),AUD1LEN-INTREQ(a0)
.2: tst.b mt_chan3+n_enable(a4)
beq .3
move.l mt_chan3+n_loopstart(a4),AUD2LC-INTREQ(a0)
move.w mt_chan3+n_replen(a4),AUD2LEN-INTREQ(a0)
.3: tst.b mt_chan4+n_enable(a4)
beq .4
move.l mt_chan4+n_loopstart(a4),AUD3LC-INTREQ(a0)
move.w mt_chan4+n_replen(a4),AUD3LEN-INTREQ(a0)
.4:
endc
move.l (sp)+,a4
rts
endc ; !NO_TIMERS
else !OSCOMPAT
;---------------------------------------------------------------------------
xdef _mt_install
xdef _mt_install_cia
_mt_install:
_mt_install_cia:
; Install a CIA-B interrupt for calling mt_music.
; a6 = CUSTOM
; a0 = VectorBase
; d0 = PALflag.b (0 is NTSC)
ifnd SDATA
move.l a4,-(sp)
lea mt_data(pc),a4
endc
clr.b mt_Enable(a4)
ifeq NO_TIMERS
; remember level 6 vector and interrupt enable
lea $78(a0),a0
move.l a0,mt_Lev6Int(a4)
move.w #$2000,d1
and.w INTENAR(a6),d1
or.w #$8000,d1
move.w d1,mt_Lev6Ena(a4)
; disable level 6 EXTER interrupts, set player interrupt vector
move.w #$2000,INTENA(a6)
move.l (a0),mt_oldLev6(a4)
lea mt_TimerAInt(pc),a1
move.l a1,(a0)
; reset TimerB toggle
lea TB_toggle(pc),a0
clr.b (a0)
; disable CIA-B interrupts, stop and save all timers
lea CIAB,a0
moveq #0,d1
move.b #$7f,CIAICR(a0)
move.b d1,CIACRA(a0)
move.b d1,CIACRB(a0)
lea mt_oldtimers(a4),a1
move.b CIATALO(a0),(a1)+
move.b CIATAHI(a0),(a1)+
move.b CIATBLO(a0),(a1)+
move.b CIATBHI(a0),(a1)
ifeq VBLANK_MUSIC
; determine if 02 clock for timers is based on PAL or NTSC
tst.b d0
bne .1
move.l #1789773,d0 ; NTSC
bra .2
.1: move.l #1773447,d0 ; PAL
.2: move.l d0,mt_timerval(a4)
; load TimerA in continuous mode for the default tempo of 125
divu #125,d0
move.b d0,CIATALO(a0)
lsr.w #8,d0
move.b d0,CIATAHI(a0)
move.b #$11,CIACRA(a0) ; load timer, start continuous
endc ; !VBLANK_MUSIC
; load TimerB with DMADELAY ticks for setting DMA and repeat
move.b #DMADELAY&255,CIATBLO(a0)
move.b #DMADELAY>>8,CIATBHI(a0)
; Ack. pending interrupts, TimerA and TimerB interrupt enable
tst.b CIAICR(a0)
move.w #$2000,INTREQ(a6)
ifne VBLANK_MUSIC
move.b #$82,CIAICR(a0) ; TimerB only for VBLANK_MUSIC
else
move.b #$83,CIAICR(a0)
endc
; enable level 6 interrupts
move.w #$a000,INTENA(a6)
endc ; !NO_TIMERS
bra mt_reset
;---------------------------------------------------------------------------
xdef _mt_remove_cia
xdef _mt_remove
_mt_remove_cia:
_mt_remove:
; Remove CIA-B music interrupt and restore the old vector.
; a6 = CUSTOM
ifeq NO_TIMERS
ifnd SDATA
move.l a4,-(sp)
lea mt_data(pc),a4
endc
; disable level 6 and CIA-B interrupts
lea CIAB,a0
move.w #$2000,d0
move.b #$7f,CIAICR(a0)
move.w d0,INTENA(a6)
tst.b CIAICR(a0)
move.w d0,INTREQ(a6)
; restore old timer values
lea mt_oldtimers(a4),a1
move.b (a1)+,CIATALO(a0)
move.b (a1)+,CIATAHI(a0)
move.b (a1)+,CIATBLO(a0)
move.b (a1),CIATBHI(a0)
move.b #$10,CIACRA(a0)
move.b #$10,CIACRB(a0)
; restore original level 6 interrupt vector
move.l mt_Lev6Int(a4),a1
move.l mt_oldLev6(a4),(a1)
; reenable CIA-B ALRM interrupt, which was set by AmigaOS
move.b #$84,CIAICR(a0)
; reenable previous level 6 interrupt
move.w mt_Lev6Ena(a4),INTENA(a6)
ifnd SDATA
move.l (sp)+,a4
endc
endc ; !NO_TIMERS
rts
ifeq NO_TIMERS
;---------------------------------------------------------------------------
mt_TimerAInt:
; TimerA interrupt calls mt_music at a selectable tempo (Fxx command),
; which defaults to 50 times per second.
; check for TB interrupt and clear CIAB interrupt flags
btst #1,CIAB+CIAICR
bne mt_TimerBInt
ifne VBLANK_MUSIC
move.w #$2000,CUSTOM+INTREQ ; ack interrupt and leave
nop
rte
else
; Now it should be a TA interrupt.
; Other level 6 interrupt sources have to be handled elsewhere.
movem.l d0-d7/a0-a6,-(sp)
lea CUSTOM,a6
ifd SDATA
lea _LinkerDB,a4
else
lea mt_data(pc),a4
endc
; clear EXTER interrupt flag
move.w #$2000,INTREQ(a6)
; do music when enabled
tst.b mt_Enable(a4)
ifeq MINIMAL
beq .2
else
beq .1
endc
bsr mt_music ; music with sfx inserted
.1: movem.l (sp)+,d0-d7/a0-a6
nop
rte
ifeq MINIMAL
.2: bsr mt_sfxonly ; no music, only sfx
movem.l (sp)+,d0-d7/a0-a6
nop
rte
endc
endc ; !VBLANK_MUSIC
;---------------------------------------------------------------------------
mt_TimerBInt:
; Handle one-shot TimerB interrupt.
; TB_toggle-technique suggested by Ross/EAB.
move.l a0,-(sp)
lea TB_toggle(pc),a0
not.b (a0)
lea CUSTOM+INTREQ,a0
beq mt_TimerBsetrep
; restart timer for repeat, enable audio DMA after DMADELAY ticks
move.w #$2000,(a0) ; clear EXTER interrupt flag
move.b #$19,CIAB+CIACRB
move.w mt_dmaon(pc),DMACON-INTREQ(a0)
move.l (sp)+,a0
nop
rte
mt_TimerBsetrep:
; Oneshot TimerB interrupt to set repeat samples after another DMADELAY ticks.
; clear EXTER and possible audio interrupt flags
move.l a4,-(sp)
move.l d0,a4
moveq #$2000>>7,d0 ; EXTER-flag
or.b mt_dmaon+1(pc),d0
lsl.w #7,d0
move.w d0,(a0)
move.l a4,d0
ifd SDATA
lea _LinkerDB,a4
else
lea mt_data(pc),a4
endc
; set repeat sample pointers and lengths
ifne MINIMAL
move.l mt_chan1+n_loopstart(a4),AUD0LC-INTREQ(a0)
move.w mt_chan1+n_replen(a4),AUD0LEN-INTREQ(a0)
move.l mt_chan2+n_loopstart(a4),AUD1LC-INTREQ(a0)
move.w mt_chan2+n_replen(a4),AUD1LEN-INTREQ(a0)
move.l mt_chan3+n_loopstart(a4),AUD2LC-INTREQ(a0)
move.w mt_chan3+n_replen(a4),AUD2LEN-INTREQ(a0)
move.l mt_chan4+n_loopstart(a4),AUD3LC-INTREQ(a0)
move.w mt_chan4+n_replen(a4),AUD3LEN-INTREQ(a0)
else ; !MINIMAL
tst.b mt_chan1+n_enable(a4)
beq .1
move.l mt_chan1+n_loopstart(a4),AUD0LC-INTREQ(a0)
move.w mt_chan1+n_replen(a4),AUD0LEN-INTREQ(a0)
.1: tst.b mt_chan2+n_enable(a4)
beq .2
move.l mt_chan2+n_loopstart(a4),AUD1LC-INTREQ(a0)
move.w mt_chan2+n_replen(a4),AUD1LEN-INTREQ(a0)
.2: tst.b mt_chan3+n_enable(a4)
beq .3
move.l mt_chan3+n_loopstart(a4),AUD2LC-INTREQ(a0)
move.w mt_chan3+n_replen(a4),AUD2LEN-INTREQ(a0)
.3: tst.b mt_chan4+n_enable(a4)
beq .4
move.l mt_chan4+n_loopstart(a4),AUD3LC-INTREQ(a0)
move.w mt_chan4+n_replen(a4),AUD3LEN-INTREQ(a0)
.4:
endc
move.l (sp)+,a4
move.l (sp)+,a0
nop
rte
TB_toggle:
dc.b 0
even
endc ; !NO_TIMERS
endc ; !OSCOMPAT
ifne NO_TIMERS
;---------------------------------------------------------------------------
xdef _mt_dmaon
_mt_dmaon:
; Enable audio DMA after some delay.
; Called by main program, for example out of a copper-interrupt.
move.w mt_dmaon(pc),CUSTOM+DMACON
rts
xdef _mt_setrep
_mt_setrep:
; Set repeat sample pointers and lengths.
; Called by main program, for example out of a copper-interrupt.
movem.l a0/a4,-(sp)
; reset audio interrupt flags
lea CUSTOM+INTREQ,a0
move.l d0,a4
move.w mt_dmaon(pc),d0
lsl.w #7,d0
move.w d0,(a0)
move.l a4,d0
ifd SDATA
lea _LinkerDB,a4
else
lea mt_data(pc),a4
endc
; set repeat sample pointers and lengths
ifne MINIMAL
move.l mt_chan1+n_loopstart(a4),AUD0LC-INTREQ(a0)
move.w mt_chan1+n_replen(a4),AUD0LEN-INTREQ(a0)
move.l mt_chan2+n_loopstart(a4),AUD1LC-INTREQ(a0)
move.w mt_chan2+n_replen(a4),AUD1LEN-INTREQ(a0)
move.l mt_chan3+n_loopstart(a4),AUD2LC-INTREQ(a0)
move.w mt_chan3+n_replen(a4),AUD2LEN-INTREQ(a0)
move.l mt_chan4+n_loopstart(a4),AUD3LC-INTREQ(a0)
move.w mt_chan4+n_replen(a4),AUD3LEN-INTREQ(a0)
else ; !MINIMAL
tst.b mt_chan1+n_enable(a4)
beq .1
move.l mt_chan1+n_loopstart(a4),AUD0LC-INTREQ(a0)
move.w mt_chan1+n_replen(a4),AUD0LEN-INTREQ(a0)
.1: tst.b mt_chan2+n_enable(a4)
beq .2
move.l mt_chan2+n_loopstart(a4),AUD1LC-INTREQ(a0)
move.w mt_chan2+n_replen(a4),AUD1LEN-INTREQ(a0)
.2: tst.b mt_chan3+n_enable(a4)
beq .3
move.l mt_chan3+n_loopstart(a4),AUD2LC-INTREQ(a0)
move.w mt_chan3+n_replen(a4),AUD2LEN-INTREQ(a0)
.3: tst.b mt_chan4+n_enable(a4)
beq .4
move.l mt_chan4+n_loopstart(a4),AUD3LC-INTREQ(a0)
move.w mt_chan4+n_replen(a4),AUD3LEN-INTREQ(a0)
.4:
endc
movem.l (sp)+,a0/a4
rts
endc ; NO_TIMERS
mt_dmaon:
dc.w $8000
;---------------------------------------------------------------------------
xdef _mt_init
_mt_init:
; Initialize new module.
; Reset speed to 6, tempo to 125 and start at given song position.
; Master volume is at 64 (maximum).
; a6 = CUSTOM
; a0 = module pointer
; a1 = sample pointer (NULL means samples are stored within the module)
; d0 = initial song position
ifnd SDATA
move.l a4,-(sp)
lea mt_data(pc),a4
endc
move.l a0,mt_mod(a4)
movem.l d2/a2,-(sp)
; set initial song position
cmp.b 950(a0),d0
blo .1
moveq #0,d0
.1: move.b d0,mt_SongPos(a4)
move.l a1,d0 ; sample data location is given?
bne .4
; get number of highest pattern
lea 952(a0),a1 ; song arrangement list
moveq #127,d0
moveq #0,d2
.2: move.b (a1)+,d1
cmp.b d2,d1
bls .3
move.b d1,d2
.3: dbf d0,.2
addq.b #1,d2 ; number of patterns
; now we can calculate the base address of the sample data
moveq #10,d0
asl.l d0,d2
lea (a0,d2.l),a1
add.w #1084,a1
; save start address of each sample and do some fixes for broken mods
.4: lea mt_SampleStarts(a4),a2
moveq #1,d2
moveq #31-1,d0
.5: move.l a1,(a2)+
moveq #0,d1
move.w 42(a0),d1
cmp.w d2,d1 ; length 0 and 1 define unused samples
bls .6
add.l d1,d1
add.l d1,a1
bra .7
.6: clr.w 42(a0) ; length 1 means zero -> no sample
.7: lea 30(a0),a0
dbf d0,.5
movem.l (sp)+,d2/a2
ifeq VBLANK_MUSIC
; reset CIA timer A to default (125)
move.l mt_timerval(a4),d0
divu #125,d0
move.b d0,CIAB+CIATALO
lsr.w #8,d0
move.b d0,CIAB+CIATAHI
endc
mt_reset:
; a4 must be initialized with base register
; reset speed and counters
move.b #6,mt_Speed(a4)
clr.b mt_Counter(a4)
clr.w mt_PatternPos(a4)
clr.b mt_PattDelTime(a4)
clr.b mt_PattDelTime2(a4)
clr.w mt_PBreakPos(a4)
clr.b mt_PBreakFlag(a4)
clr.b mt_PosJumpFlag(a4)
; disable the filter
or.b #2,CIAA+CIAPRA
ifeq MINIMAL
; set master volume to 64
lea MasterVolTab64(pc),a0
move.l a0,mt_MasterVolTab(a4)
endc
; set channel index
clr.b mt_chan1+n_index(a4)
move.b #1,mt_chan2+n_index(a4)
move.b #2,mt_chan3+n_index(a4)
move.b #3,mt_chan4+n_index(a4)
; initialize channel DMA and interrupt bits
move.w #$0001,mt_chan1+n_dmabit(a4)
move.w #$0002,mt_chan2+n_dmabit(a4)
move.w #$0004,mt_chan3+n_dmabit(a4)
move.w #$0008,mt_chan4+n_dmabit(a4)
move.w #$0080,mt_chan1+n_intbit(a4)
move.w #$0100,mt_chan2+n_intbit(a4)
move.w #$0200,mt_chan3+n_intbit(a4)
move.w #$0400,mt_chan4+n_intbit(a4)
clr.b mt_E8Trigger(a4)
ifeq MINIMAL
clr.b mt_SongEnd(a4)
clr.b mt_SilCntValid(a4)
endc
ifnd SDATA
move.l (sp)+,a4
endc
;---------------------------------------------------------------------------
xdef _mt_end
_mt_end:
; Stop playing current module.
; a6 = CUSTOM
DISABLE
ifd SDATA
clr.b mt_Enable(a4)
lea mt_chan1(a4),a0
bsr resetch
lea mt_chan2(a4),a0
bsr resetch
lea mt_chan3(a4),a0
bsr resetch
lea mt_chan4(a4),a0
bsr resetch
else
lea mt_data(pc),a1
clr.b mt_Enable(a1)
lea mt_chan1(a1),a0
bsr resetch
lea mt_chan2(a1),a0
bsr resetch
lea mt_chan3(a1),a0
bsr resetch
lea mt_chan4(a1),a0
bsr resetch
endc
moveq #0,d0
move.w d0,AUD0VOL(a6)
move.w d0,AUD1VOL(a6)
move.w d0,AUD2VOL(a6)
move.w d0,AUD3VOL(a6)
move.w #$000f,DMACON(a6)
ENABLE
rts
resetch:
; a0 = channel status
; All registers are preserved!
move.w #320,n_period(a0) ; make sure period is not illegal
clr.w n_volume(a0)
clr.w n_sfxlen(a0)
clr.w n_funk(a0)
clr.b n_sfxpri(a0)
clr.b n_looped(a0)
clr.b n_gliss(a0)
ifeq MINIMAL
clr.b n_musiconly(a0)
st n_enable(a0)
endc
rts
ifeq MINIMAL
;---------------------------------------------------------------------------
xdef _mt_soundfx
_mt_soundfx:
; Request playing of an external sound effect on the most unused channel.
; This function is for compatibility with the old API only!
; You should call _mt_playfx instead!
; a6 = CUSTOM
; a0 = sample pointer
; d0.w = sample length in words
; d1.w = sample period
; d2.w = sample volume
lea -sfx_sizeof(sp),sp
move.l a0,sfx_ptr(sp)
movem.w d0-d2,sfx_len(sp)
move.w #$ff01,sfx_cha(sp) ; any channel, priority=1
move.l sp,a0
bsr _mt_playfx
lea sfx_sizeof(sp),sp
rts
;---------------------------------------------------------------------------
xdef _mt_playfx
_mt_playfx:
; Request playing of a prioritized external sound effect, either on a
; fixed channel or on the most unused one.
; A negative channel specification means to use the best one.
; The priority is unsigned and should be greater than zero.
; This channel will be blocked for music until the effect has finished.
; a6 = CUSTOM
; a0 = sfx-structure pointer with the following layout:
; 0: ptr, 4: len.w, 6: period.w, 8: vol.w, 10: channel.b, 11: priority.b
; -> d0 = pointer to channel status or NULL when sample was ignored
ifd SDATA
movem.l d2-d7/a0-a3/a5,-(sp)
else
movem.l d2-d7/a0-a5,-(sp)
lea mt_data(pc),a4
endc
DISABLE
moveq #0,d0
move.b sfx_cha(a0),d0
bpl channelsfx ; use fixed channel for effect
; Did we already calculate the n_freecnt values for all channels?
tst.b mt_SilCntValid(a4)
bne freecnt_valid
; Look at the next 8 pattern steps to find the longest sequence
; of silence (no new note or instrument).
moveq #8,d2
move.l #$fffff000,d3 ; mask to ignore effects
; reset freecnts for all channels
moveq #0,d0
move.b d0,mt_chan1+n_freecnt(a4)
move.b d0,mt_chan2+n_freecnt(a4)
move.b d0,mt_chan3+n_freecnt(a4)
move.b d0,mt_chan4+n_freecnt(a4)
moveq #-1,d4
moveq #-1,d5
moveq #-1,d6
moveq #-1,d7
; get pattern pointer
move.l mt_mod(a4),a3 ; a3 mod pointer
lea 952(a3),a5 ; a5 song arrangement list
move.w mt_PatternPos(a4),d1
move.b mt_SongPos(a4),d0
.1: move.b (a5,d0.w),d0
swap d0
lea 1084(a3),a1
lsr.l #6,d0
add.l d0,a1
lea 1024(a1),a2 ; a2 end of pattern
add.w d1,a1 ; a1 current pattern pos
.2: moveq #0,d0
move.l (a1)+,d1
and.l d3,d1
seq d1
and.b d1,d4
sub.b d4,mt_chan1+n_freecnt(a4)
add.b d4,d0
move.l (a1)+,d1
and.l d3,d1
seq d1
and.b d1,d5
sub.b d5,mt_chan2+n_freecnt(a4)
add.b d5,d0
move.l (a1)+,d1
and.l d3,d1
seq d1
and.b d1,d6
sub.b d6,mt_chan3+n_freecnt(a4)
add.b d6,d0
move.l (a1)+,d1
and.l d3,d1
seq d1
and.b d1,d7
sub.b d7,mt_chan4+n_freecnt(a4)
add.b d7,d0
; break the loop when no channel has any more free pattern steps
beq .7
; otherwise break after 8 pattern steps
subq.w #1,d2
beq .7
; End of pattern reached? Then load next pattern pointer.
cmp.l a2,a1
blo .2
moveq #0,d1
moveq #1,d0
add.b mt_SongPos(a4),d0
and.w #$007f,d0
cmp.b 950(a3),d0 ; end of song reached?
blo .1
moveq #0,d0
bra .1
.7: st mt_SilCntValid(a4)
freecnt_valid:
sub.l a2,a2
move.b sfx_pri(a0),d2
; Determine which channels are already allocated for sound
; effects and check if the limit was reached. In this case only
; replace sound effect channels by higher priority.
moveq #3,d0
sub.b mt_MusicChannels(a4),d0
move.b mt_chan1+n_sfxpri(a4),d4
or.b mt_chan1+n_musiconly(a4),d4
sne d1
add.b d1,d0
move.b mt_chan2+n_sfxpri(a4),d5
or.b mt_chan2+n_musiconly(a4),d5
sne d1
add.b d1,d0
move.b mt_chan3+n_sfxpri(a4),d6
or.b mt_chan3+n_musiconly(a4),d6
sne d1
add.b d1,d0
move.b mt_chan4+n_sfxpri(a4),d7
or.b mt_chan4+n_musiconly(a4),d7
sne d1
add.b d1,d0
bmi .overwrite ; all channels reserved/playing effects
; We will prefer a music channel which had an audio interrupt,
; because that means the last instrument sample has been played
; completely, and the channel is now in an idle loop.
; Also exclude channels which have set a repeat loop.
; Try not to break them!
moveq #0,d3
tst.b mt_chan1+n_looped(a4)
bne .1
or.w #$0080,d3
.1: tst.b mt_chan2+n_looped(a4)
bne .2
or.w #$0100,d3
.2: tst.b mt_chan3+n_looped(a4)
bne .3
or.w #$0200,d3
.3: tst.b mt_chan4+n_looped(a4)
bne .4
or.w #$0400,d3
.4: move.w INTREQR(a6),d1
and.w d3,d1
bne .6
; All channels are busy. Then break the non-looped ones first...
move.w d3,d1
bne .6
; ..except there are none. Then it doesn't matter. :|
move.w #$0780,d1
; first look for the best unused channel
.6: moveq #0,d3
btst #7,d1
seq d0
or.b d4,d0
bne .7
lea mt_chan1+n_freecnt(a4),a1
cmp.b (a1),d3
bhi .7
move.l a1,a2
move.b (a1),d3
.7: btst #8,d1
seq d0
or.b d5,d0
bne .8
lea mt_chan2+n_freecnt(a4),a1
cmp.b (a1),d3
bhi .8
move.l a1,a2
move.b (a1),d3
.8: btst #9,d1
seq d0
or.b d6,d0
bne .9
lea mt_chan3+n_freecnt(a4),a1
cmp.b (a1),d3
bhi .9
move.l a1,a2
move.b (a1),d3
.9: btst #10,d1
seq d0
or.b d7,d0
bne .10
lea mt_chan4+n_freecnt(a4),a1
cmp.b (a1),d3
bhi .10
move.l a1,a2
bra found_sfx_ch
.10: move.l a2,d3
bne found_sfx_ch
.overwrite:
; finally try to overwrite a sound effect with lower/equal priority
moveq #0,d3
tst.b d4
beq .11
cmp.b d4,d2
blo .11
lea mt_chan1+n_freecnt(a4),a2
move.b (a2),d3
.11: tst.b d5
beq .12
cmp.b d5,d2
blo .12
lea mt_chan2+n_freecnt(a4),a1
cmp.b (a1),d3
bhi .12
move.l a1,a2
move.b (a1),d3
.12: tst.b d6
beq .13
cmp.b d6,d2
blo .13
lea mt_chan3+n_freecnt(a4),a1
cmp.b (a1),d3
bhi .13
move.l a1,a2
move.b (a1),d3
.13: tst.b d7
beq .14
cmp.b d7,d2
blo .14
lea mt_chan4+n_freecnt(a4),a1
cmp.b (a1),d3
bhi .14
move.l a1,a2
.14: move.l a2,d3
beq exit_playfx ; ignore new sfx due to low priority
found_sfx_ch:
lea -n_freecnt(a2),a2
bra set_sfx
channelsfx:
; a0 = sfx structure
; d0 = fixed channel for new sound effect
add.w d0,d0
lea mt_chan1(a4),a2
add.w channel_offsets(pc,d0.w),a2
; priority high enough to replace a present effect on this channel?
move.b sfx_pri(a0),d2
cmp.b n_sfxpri(a2),d2
bhs set_sfx
sub.l a2,a2
bra exit_playfx
set_sfx:
; activate the sound effect on this channel
; a0 = sfx structure
; d2 = sfx priority
; a2 = channel status
move.l (a0)+,n_sfxptr(a2) ; sfx_ptr
move.w (a0)+,n_sfxlen(a2) ; sfx_len
move.w (a0)+,n_sfxper(a2) ; sfx_per
move.w (a0),n_sfxvol(a2) ; sfx_vol
move.b d2,n_sfxpri(a2)
exit_playfx:
ENABLE
move.l a2,d0 ; ptr to selected channel or NULL
ifd SDATA
movem.l (sp)+,d2-d7/a0-a3/a5
else
movem.l (sp)+,d2-d7/a0-a5
endc
rts
channel_offsets:
dc.w 0*n_sizeof,1*n_sizeof,2*n_sizeof,3*n_sizeof
;---------------------------------------------------------------------------
xdef _mt_loopfx
_mt_loopfx:
; Request playing of a looped sound effect on a fixed channel, which
; will be blocked for music until the effect is stopped (_mt_stopfx).
; It uses the same sfx-structure as _mt_playfx, but the priority is
; ignored. A looped sound effect has always highest priority and will
; replace a previous effect on the same channel. No automatic channel
; selection possible!
; Also make sure the sample starts with a zero-word, which is used
; for idling when the effect is stopped. This word is included in the
; total length calculation, but excluded when actually playing the loop.
; a6 = CUSTOM
; a0 = sfx-structure pointer with the following layout:
; 0: ptr, 4: len.w, 6: period.w, 8: vol.w, 10: channel.b
ifnd SDATA
lea mt_data+mt_chan1(pc),a1
else
lea mt_chan1(a4),a1
endc
moveq #3,d0
and.b sfx_cha(a0),d0
add.w d0,d0
add.w channel_offsets(pc,d0.w),a1
DISABLE
move.l (a0)+,n_sfxptr(a1) ; sfx_ptr
move.w (a0)+,n_sfxlen(a1) ; sfx_len
move.w (a0)+,n_sfxper(a1) ; sfx_per
move.w (a0),n_sfxvol(a1) ; sfx_vol
st n_sfxpri(a1) ; sfx_pri -1 enables looped mode
ENABLE
rts
;---------------------------------------------------------------------------
xdef _mt_stopfx
_mt_stopfx:
; Immediately stop a currently playing sound effect on a channel.
; a6 = CUSTOM
; d0.b = channel (0..3)
ifnd SDATA
lea mt_data+mt_chan1(pc),a0
else
lea mt_chan1(a4),a0
endc
and.w #3,d0
add.w d0,d0
add.w channel_offsets(pc,d0.w),a0
DISABLE
tst.b n_sfxpri(a0)
beq .1 ; no sfx playing anyway
moveq #1,d0
move.b d0,n_sfxpri(a0)
move.w d0,n_sfxlen(a0) ; idle loop
move.w #108,n_sfxper(a0) ; enter idle as quickly as possible
clr.w n_sfxvol(a0) ; and cut volume
ifne NULL_IS_CLEARED
clr.b n_looped(a0)
clr.l n_sfxptr(a0) ; use $0 for idle-looping
else
tst.b n_looped(a0)
beq .1
clr.b n_looped(a0)
subq.l #2,n_sfxptr(a0) ; idle loop at sample-start - 2
endc
.1: ENABLE
rts
;---------------------------------------------------------------------------
xdef _mt_musicmask
_mt_musicmask:
; Set bits in the mask define which specific channels are reserved
; for music only.
; a6 = CUSTOM
; d0.b = channel-mask (bit 0 for channel 0, ..., bit 3 for channel 3)
ifnd SDATA
move.l a4,-(sp)
lea mt_data(pc),a4
endc
DISABLE
lsl.b #5,d0
scs mt_chan4+n_musiconly(a4)
add.b d0,d0
scs mt_chan3+n_musiconly(a4)
add.b d0,d0
scs mt_chan2+n_musiconly(a4)
add.b d0,d0
scs mt_chan1+n_musiconly(a4)
ENABLE
ifnd SDATA
move.l (sp)+,a4
endc
rts
;---------------------------------------------------------------------------
xdef _mt_mastervol
_mt_mastervol:
; Set a master volume from 0 to 64 for all music channels.
; Note that the master volume does not affect the volume of external
; sound effects (which is desired).
; a6 = CUSTOM
; d0.w = master volume
ifnd SDATA
move.l a4,-(sp)
lea mt_data(pc),a4
endc
; stingray, since each volume table has a size of 65 bytes
; we simply multiply (optimised of course) by 65 to get the
; offset to the correct table
lea MasterVolTab0(pc),a0
add.w d0,a0
lsl.w #6,d0
add.w d0,a0
; set new table and adapt all channel volumes immediately
DISABLE
move.l a0,mt_MasterVolTab(a4)
bsr set_all_volumes
ENABLE
ifnd SDATA
move.l (sp)+,a4
endc
rts
;---------------------------------------------------------------------------
xdef _mt_channelmask
_mt_channelmask:
; Define which music channels are muted. Doesn't affect sound effects.
; a6 = CUSTOM
; d0.b = channel-mask (bit 0 for channel 0, ..., bit 3 for channel 3)
ifnd SDATA
move.l a4,-(sp)
lea mt_data(pc),a4
endc
DISABLE
lsl.b #5,d0
scs mt_chan4+n_enable(a4)
add.b d0,d0
scs mt_chan3+n_enable(a4)
add.b d0,d0
scs mt_chan2+n_enable(a4)
add.b d0,d0
scs mt_chan1+n_enable(a4)
move.l mt_MasterVolTab(a4),a0
bsr set_all_volumes
ENABLE
ifnd SDATA
move.l (sp)+,a4
endc
rts
;---------------------------------------------------------------------------
set_all_volumes:
; a0 = Master volume table
tst.b mt_chan1+n_sfxpri(a4)
bne .1
move.w mt_chan1+n_volume(a4),d0
move.b (a0,d0.w),d0
and.b mt_chan1+n_enable(a4),d0
move.w d0,AUD0VOL(a6)
ifd VOL0_UNLOOPS
bne .1
clr.b mt_chan1+n_looped(a4)
endc
.1: tst.b mt_chan2+n_sfxpri(a4)
bne .2
move.w mt_chan2+n_volume(a4),d0
move.b (a0,d0.w),d0
and.b mt_chan2+n_enable(a4),d0
move.w d0,AUD1VOL(a6)
ifd VOL0_UNLOOPS
bne .2
clr.b mt_chan2+n_looped(a4)
endc
.2: tst.b mt_chan3+n_sfxpri(a4)
bne .3
move.w mt_chan3+n_volume(a4),d0
move.b (a0,d0.w),d0
and.b mt_chan3+n_enable(a4),d0
move.w d0,AUD2VOL(a6)
ifd VOL0_UNLOOPS
bne .3
clr.b mt_chan3+n_looped(a4)
endc
.3: tst.b mt_chan4+n_sfxpri(a4)
bne .4
move.w mt_chan4+n_volume(a4),d0
move.b (a0,d0.w),d0
and.b mt_chan4+n_enable(a4),d0
move.w d0,AUD3VOL(a6)
ifd VOL0_UNLOOPS
bne .4
clr.b mt_chan4+n_looped(a4)
endc
.4: rts
;---------------------------------------------------------------------------
xdef _mt_samplevol
_mt_samplevol:
; Redefine a sample's volume. May also be done while the song is playing.
; Warning: Does not check arguments for valid range! You must have done
; _mt_init before calling this function!
; The new volume is persistent. Even when the song is restarted.
; d0.w = sample number (0-31)
; d1.b = volume (0-64)
ifd SDATA
move.l mt_mod(a4),a0
else
move.l mt_data+mt_mod(pc),a0
endc
swap d1
move.w d0,d1
add.w d1,d1
lsl.w #5,d0
sub.w d1,d0 ; table index: sample number * 30
swap d1
move.b d1,12+3(a0,d0.w) ; set sample's volume
rts
endc ; !MINIMAL
;---------------------------------------------------------------------------
xdef _mt_music
_mt_music:
; Used when called by external interrupt handler.
; Saves/restores all registers and sets up A4 when needed.
; a6 = CUSTOM
ifd SDATA
movem.l d2-d7/a2-a3/a5,-(sp)
bsr mt_music
movem.l (sp)+,d2-d7/a2-a3/a5
else ; !SDATA
movem.l d2-d7/a2-a5,-(sp)
lea mt_data(pc),a4
bsr mt_music
movem.l (sp)+,d2-d7/a2-a5
endc
rts
mt_music:
; Called from interrupt.
; Play next position when Counter equals Speed.
; Effects are always handled.
; a6 = CUSTOM
moveq #0,d7 ; d7 is always zero
lea mt_dmaon+1(pc),a0
move.b d7,(a0)
addq.b #1,mt_Counter(a4)
move.b mt_Counter(a4),d0
cmp.b mt_Speed(a4),d0
blo no_new_note
; handle a new note
move.b d7,mt_Counter(a4)
tst.b mt_PattDelTime2(a4)
beq get_new_note
; we have a pattern delay, check effects then step
lea AUD0LC(a6),a5
lea mt_chan1(a4),a2
bsr mt_checkfx
lea AUD1LC(a6),a5
lea mt_chan2(a4),a2
bsr mt_checkfx
lea AUD2LC(a6),a5
lea mt_chan3(a4),a2
bsr mt_checkfx
lea AUD3LC(a6),a5
lea mt_chan4(a4),a2
bsr mt_checkfx
bra settb_step
no_new_note:
; no new note, just check effects, don't step to next position
lea AUD0LC(a6),a5
lea mt_chan1(a4),a2
bsr mt_checkfx
lea AUD1LC(a6),a5
lea mt_chan2(a4),a2
bsr mt_checkfx
lea AUD2LC(a6),a5
lea mt_chan3(a4),a2
bsr mt_checkfx
lea AUD3LC(a6),a5
lea mt_chan4(a4),a2
bsr mt_checkfx
ifeq NO_TIMERS
; set one-shot TimerB interrupt for enabling DMA, when needed
move.b mt_dmaon+1(pc),d0
beq same_pattern
move.b #$19,CIAB+CIACRB ; load/start timer B, one-shot
endc
bra same_pattern
get_new_note:
; determine pointer to current pattern line
move.l mt_mod(a4),a0
lea 12(a0),a3 ; sample info table
lea 1084(a0),a1 ; pattern data
lea 952(a0),a0
moveq #0,d0
move.b mt_SongPos(a4),d0
move.b (a0,d0.w),d0 ; current pattern number
swap d0
lsr.l #6,d0
add.l d0,a1 ; pattern base
add.w mt_PatternPos(a4),a1 ; a1 pattern line
; play new note for each channel, apply some effects
lea AUD0LC(a6),a5
lea mt_chan1(a4),a2
bsr mt_playvoice
lea AUD1LC(a6),a5
lea mt_chan2(a4),a2
bsr mt_playvoice
lea AUD2LC(a6),a5
lea mt_chan3(a4),a2
bsr mt_playvoice
lea AUD3LC(a6),a5
lea mt_chan4(a4),a2
bsr mt_playvoice
settb_step:
ifeq NO_TIMERS
; set one-shot TimerB interrupt for enabling DMA, when needed
move.b mt_dmaon+1(pc),d0
beq pattern_step
move.b #$19,CIAB+CIACRB ; load/start timer B, one-shot
endc
pattern_step:
; next pattern line, handle delay and break
ifeq MINIMAL
clr.b mt_SilCntValid(a4) ; recalculate silence counters
endc
moveq #16,d2 ; offset to next pattern line
move.b mt_PattDelTime2(a4),d1
move.b mt_PattDelTime(a4),d0
beq .1
move.b d0,d1
move.b d7,mt_PattDelTime(a4)
.1: tst.b d1
beq .3
subq.b #1,d1
beq .2
moveq #0,d2 ; do not advance to next line
.2: move.b d1,mt_PattDelTime2(a4)
.3: add.w mt_PatternPos(a4),d2 ; d2 PatternPos
; check for break
bclr d7,mt_PBreakFlag(a4)
beq .4
move.w mt_PBreakPos(a4),d2
move.w d7,mt_PBreakPos(a4)
; check whether end of pattern is reached
.4: move.w d2,mt_PatternPos(a4)
cmp.w #1024,d2
blo same_pattern
song_step:
move.w mt_PBreakPos(a4),mt_PatternPos(a4)
move.w d7,mt_PBreakPos(a4)
move.b d7,mt_PosJumpFlag(a4)
; next position in song
moveq #1,d0
add.b mt_SongPos(a4),d0
and.w #$007f,d0
move.l mt_mod(a4),a0
cmp.b 950(a0),d0 ; end of song reached?
blo .1
moveq #0,d0 ; restart the song from the beginning
ifeq MINIMAL
addq.b #1,mt_SongEnd(a4)
bne .2
clr.b mt_Enable(a4) ; stop the song when mt_SongEnd was -1
.2: and.b #$7f,mt_SongEnd(a4)
endc
.1: move.b d0,mt_SongPos(a4)
same_pattern:
tst.b mt_PosJumpFlag(a4)
bne song_step
rts
ifeq MINIMAL
;---------------------------------------------------------------------------
mt_sfxonly:
; Called from interrupt.
; Plays sound effects on free channels.
; a6 = CUSTOM
moveq #0,d7 ; d7 is always zero
lea mt_dmaon+1(pc),a0
move.b d7,(a0)
lea AUD0LC(a6),a5
lea mt_chan1(a4),a2
bsr chan_sfx_only
lea AUD1LC(a6),a5
lea mt_chan2(a4),a2
bsr chan_sfx_only
lea AUD2LC(a6),a5
lea mt_chan3(a4),a2
bsr chan_sfx_only
lea AUD3LC(a6),a5
lea mt_chan4(a4),a2
bsr chan_sfx_only
ifeq NO_TIMERS
move.b mt_dmaon+1(pc),d0
beq .1
move.b #$19,CIAB+CIACRB ; load/start timer B, one-shot
endc
.1: rts
chan_sfx_only:
; Check for new sound samples. Check if previous ones are finished.
; a2 = channel data
; a5 = audio registers
tst.b n_sfxpri(a2)
beq .1
move.w n_sfxlen(a2),d0
bne start_sfx
tst.b n_looped(a2)
bne .1
move.w n_intbit(a2),d0
and.w INTREQR(a6),d0
beq .1
move.w n_dmabit(a2),d0
and.w mt_dmaon(pc),d0
bne .1
; last sound effect sample has played, so unblock this channel again
move.b d7,n_sfxpri(a2)
.1: rts
;---------------------------------------------------------------------------
start_sfx:
; d0 = sfx_len in words
; a2 = channel data
; a5 = audio registers
; play new sound effect on this channel
move.w n_dmabit(a2),d1
move.w d1,DMACON(a6)
move.l n_sfxptr(a2),a0
tst.b n_sfxpri(a2)
bpl .1
; looped sound effect
st n_looped(a2)
addq.l #2,a0 ; skip first word, used for idling
subq.w #1,d0
move.l a0,AUDLC(a5)
move.w d0,AUDLEN(a5)
bra .2
; normal sound effect
.1: move.b d7,n_looped(a2)
move.l a0,AUDLC(a5)
move.w d0,AUDLEN(a5)
moveq #1,d0 ; idles after playing once
ifne NULL_IS_CLEARED
sub.l a0,a0
endc
; save repeat and period for TimerB interrupt
.2: move.l a0,n_loopstart(a2)
move.w d0,n_replen(a2)
move.w n_sfxper(a2),d0
move.w d0,AUDPER(a5)
move.w d0,n_period(a2)
move.w n_sfxvol(a2),AUDVOL(a5)
move.w d7,n_sfxlen(a2) ; don't call start_sfx again
lea mt_dmaon(pc),a0
or.w d1,(a0) ; DMA-channel to enable on TimerB
rts
endc ; !MINIMAL
;---------------------------------------------------------------------------
mt_checkfx:
; a2 = channel data
; a5 = audio registers
ifeq MINIMAL
tst.b n_sfxpri(a2)
beq .3
move.w n_sfxlen(a2),d0
beq .2
bsr start_sfx
; channel is blocked, only check some E-commands
.1: move.w #$0fff,d4
and.w n_cmd(a2),d4
move.w d4,d0
clr.b d0
cmp.w #$0e00,d0
bne mt_nop
and.w #$00ff,d4
bra blocked_e_cmds
.2: tst.b n_looped(a2)
bne .1
move.w n_intbit(a2),d0
and.w INTREQR(a6),d0
beq .1
move.w n_dmabit(a2),d0
and.w mt_dmaon(pc),d0
bne .1
; sound effect sample has played, so unblock this channel again
move.b d7,n_sfxpri(a2)
endc ; !MINIMAL
; do channel effects between notes
.3: move.w n_funk(a2),d0
beq .4
bsr mt_updatefunk
.4: move.w #$0fff,d4
and.w n_cmd(a2),d4
beq mt_pernop
and.w #$00ff,d4
moveq #$0f,d0
and.b n_cmd(a2),d0
add.w d0,d0
move.w fx_tab(pc,d0.w),d0
jmp fx_tab(pc,d0.w)
fx_tab:
dc.w mt_arpeggio-fx_tab ; $0
dc.w mt_portaup-fx_tab
dc.w mt_portadown-fx_tab
dc.w mt_toneporta-fx_tab
dc.w mt_vibrato-fx_tab ; $4
dc.w mt_tonevolslide-fx_tab
dc.w mt_vibrvolslide-fx_tab
dc.w mt_tremolo-fx_tab
dc.w mt_nop-fx_tab ; $8
dc.w mt_nop-fx_tab
dc.w mt_volumeslide-fx_tab
dc.w mt_nop-fx_tab
dc.w mt_nop-fx_tab ; $C
dc.w mt_nop-fx_tab
dc.w mt_e_cmds-fx_tab
dc.w mt_nop-fx_tab
mt_pernop:
; just set the current period
move.w n_period(a2),AUDPER(a5)
mt_nop:
rts
;---------------------------------------------------------------------------
mt_playvoice:
; a1 = pattern ptr
; a2 = channel data
; a3 = sample info table
; a5 = audio registers
move.l (a1)+,d6 ; d6 current note/cmd words
ifeq MINIMAL
; channel blocked by external sound effect?
tst.b n_sfxpri(a2)
beq .2
move.w n_sfxlen(a2),d0
beq .1
bsr start_sfx
bra moreblockedfx
; do only some limited commands, while sound effect is in progress
.1: tst.b n_looped(a2)
bne moreblockedfx
move.w n_intbit(a2),d0
and.w INTREQR(a6),d0
beq moreblockedfx
move.w n_dmabit(a2),d0
and.w mt_dmaon(pc),d0
bne moreblockedfx
; sound effect sample has played, so unblock this channel again
move.b d7,n_sfxpri(a2)
endc ; !MINIMAL
.2: tst.l (a2) ; n_note/cmd: any note or cmd set?
bne .3
move.w n_period(a2),AUDPER(a5)
.3: move.l d6,(a2)
moveq #15,d5
and.b n_cmd(a2),d5
add.w d5,d5 ; d5 cmd*2
moveq #0,d4
move.b d6,d4 ; d4 cmd argument (in MSW)
swap d4
move.w #$0ff0,d4
and.w d6,d4 ; d4 for checking E-cmd (in LSW)
swap d6
move.l d6,d0 ; S...S...
clr.b d0
rol.w #4,d0
rol.l #4,d0 ; ....00SS
and.w #$0fff,d6 ; d6 note
; get sample start address
add.w d0,d0 ; sample number * 2
beq set_regs
move.w mult30tab(pc,d0.w),d1 ; d1 sample info table offset
lea mt_SampleStarts(a4),a0
add.w d0,d0
move.l -4(a0,d0.w),d2
; read length, volume and repeat from sample info table
lea (a3,d1.w),a0
move.w (a0)+,d0 ; length
bne .4
ifne NULL_IS_CLEARED
moveq #0,d2 ; use $0 for empty samples
else
; use the first two bytes from the first sample for empty samples
move.l mt_SampleStarts(a4),d2
endc
addq.w #1,d0
.4: move.l d2,n_start(a2)
move.w d0,n_reallength(a2)
; determine period table from fine-tune parameter
moveq #0,d3
move.b (a0)+,d3
add.w d3,d3
move.l a0,d1
lea mt_PerFineTune(pc),a0
add.w (a0,d3.w),a0
move.l a0,n_pertab(a2)
move.l d1,a0
cmp.w #2*8,d3
shs n_minusft(a2)
moveq #0,d1
move.b (a0)+,d1 ; volume
move.w d1,n_volume(a2)
move.w (a0)+,d3 ; repeat offset
beq no_offset
; set repeat
add.l d3,d2
add.l d3,d2
move.w (a0),d0
move.w d0,n_replen(a2)
exg d0,d3 ; n_replen to d3
add.w d3,d0
bra set_len_start
mult30tab:
dc.w 0*30,1*30,2*30,3*30,4*30,5*30,6*30,7*30
dc.w 8*30,9*30,10*30,11*30,12*30,13*30,14*30,15*30
dc.w 16*30,17*30,18*30,19*30,20*30,21*30,22*30,23*30
dc.w 24*30,25*30,26*30,27*30,28*30,29*30,30*30,31*30
no_offset:
move.w (a0),d3
ifne NULL_IS_CLEARED
cmp.w #1,d3
beq .1
bhi set_replen
else
bne set_replen
endc
; repeat length zero means idle-looping
addq.w #1,d3
.1: moveq #0,d2 ; expect two zero bytes at $0
set_replen:
move.w d3,n_replen(a2)
set_len_start:
move.w d0,n_length(a2)
move.l d2,n_loopstart(a2)
move.l d2,n_wavestart(a2)
ifeq MINIMAL
move.l mt_MasterVolTab(a4),a0
move.b (a0,d1.w),d1
and.b n_enable(a2),d1
endc
move.w d1,AUDVOL(a5)
; remember if sample is looped
; @@@ FIXME: also need to check if n_loopstart equals n_start
subq.w #1,d3
sne n_looped(a2)
set_regs:
; d4 = cmd argument | masked E-cmd
; d5 = cmd*2
; d6 = cmd.w | note.w
move.w d4,d3 ; d3 masked E-cmd
swap d4 ; d4 cmd argument into LSW
tst.w d6
beq checkmorefx ; no new note
cmp.w #$0e50,d3
beq set_finetune
move.w prefx_tab(pc,d5.w),d0
jmp prefx_tab(pc,d0.w)
prefx_tab:
dc.w set_period-prefx_tab,set_period-prefx_tab,set_period-prefx_tab
dc.w set_toneporta-prefx_tab ; $3
dc.w set_period-prefx_tab
dc.w set_toneporta-prefx_tab ; $5
dc.w set_period-prefx_tab,set_period-prefx_tab,set_period-prefx_tab
dc.w set_sampleoffset-prefx_tab ; $9
dc.w set_period-prefx_tab,set_period-prefx_tab,set_period-prefx_tab
dc.w set_period-prefx_tab,set_period-prefx_tab,set_period-prefx_tab
mt_sampleoffset:
; cmd 9 x y (xy = offset in 256 bytes)
; d4 = xy
moveq #0,d0
move.b d4,d0
bne .1
move.b n_sampleoffset(a2),d0
bra .2
.1: move.b d0,n_sampleoffset(a2)
.2: lsl.w #7,d0
cmp.w n_length(a2),d0
bhs .3
sub.w d0,n_length(a2)
add.w d0,d0
add.l d0,n_start(a2)
rts
.3: move.w #1,n_length(a2)
rts
set_sampleoffset:
bsr mt_sampleoffset
bra set_period
set_finetune:
lea mt_PerFineTune(pc),a0
moveq #$0f,d0
and.b d4,d0
add.w d0,d0
add.w (a0,d0.w),a0
move.l a0,n_pertab(a2)
cmp.w #2*8,d0
shs n_minusft(a2)
set_period:
; find nearest period for a note value, then apply finetuning
; d3 = masked E-cmd
; d4 = cmd argument
; d5 = cmd*2
; d6 = note.w
lea mt_PeriodTable(pc),a0
moveq #36-1,d0
moveq #-2,d1
.1: addq.w #2,d1 ; table offset
cmp.w (a0)+,d6
dbhs d0,.1
; apply finetuning, set period and note-offset
move.l n_pertab(a2),a0
move.w (a0,d1.w),d2
move.w d2,n_period(a2)
move.w d1,n_noteoff(a2)
; check for notedelay
cmp.w #$0ed0,d3 ; notedelay
beq checkmorefx
; disable DMA
move.w n_dmabit(a2),d0
move.w d0,DMACON(a6)
btst #2,n_vibratoctrl(a2)
bne .2
move.b d7,n_vibratopos(a2)
.2: btst #2,n_tremoloctrl(a2)
bne .3
move.b d7,n_tremolopos(a2)
.3: move.l n_start(a2),AUDLC(a5)
move.w n_length(a2),AUDLEN(a5)
move.w d2,AUDPER(a5)
lea mt_dmaon(pc),a0
or.w d0,(a0)
checkmorefx:
; d4 = cmd argument
; d5 = cmd*2
; d6 = note.w
move.w n_funk(a2),d0
beq .1
bsr mt_updatefunk
.1: move.w morefx_tab(pc,d5.w),d0
jmp morefx_tab(pc,d0.w)
morefx_tab:
dc.w mt_pernop-morefx_tab,mt_pernop-morefx_tab,mt_pernop-morefx_tab
dc.w mt_pernop-morefx_tab,mt_pernop-morefx_tab,mt_pernop-morefx_tab
dc.w mt_pernop-morefx_tab,mt_pernop-morefx_tab,mt_pernop-morefx_tab
dc.w mt_sampleoffset-morefx_tab ; $9
dc.w mt_pernop-morefx_tab
dc.w mt_posjump-morefx_tab ; $B
dc.w mt_volchange-morefx_tab
dc.w mt_patternbrk-morefx_tab ; $D
dc.w mt_e_cmds-morefx_tab
dc.w mt_setspeed-morefx_tab
set_toneporta:
move.l n_pertab(a2),a0 ; tuned period table
; find first period which is less or equal the note in d6
moveq #36-1,d0
moveq #-2,d1
.1: addq.w #2,d1
cmp.w (a0)+,d6
dbhs d0,.1
tst.b n_minusft(a2) ; negative fine tune?
beq .2
tst.w d1
beq .2
subq.l #2,a0 ; then take previous period
subq.w #2,d1
.2: move.w d1,n_noteoff(a2) ; note offset in period table
move.w n_period(a2),d2
move.w -(a0),d1
cmp.w d1,d2
bne .3
moveq #0,d1
.3: move.w d1,n_wantedperiod(a2)
move.w n_funk(a2),d0
beq .4
bsr mt_updatefunk
.4: move.w d2,AUDPER(a5)
rts
ifeq MINIMAL
moreblockedfx:
; d6 = note.w | cmd.w
moveq #0,d4
move.b d6,d4 ; cmd argument
and.w #$0f00,d6
lsr.w #7,d6
move.w blmorefx_tab(pc,d6.w),d0
jmp blmorefx_tab(pc,d0.w)
blmorefx_tab:
dc.w mt_nop-blmorefx_tab,mt_nop-blmorefx_tab
dc.w mt_nop-blmorefx_tab,mt_nop-blmorefx_tab
dc.w mt_nop-blmorefx_tab,mt_nop-blmorefx_tab
dc.w mt_nop-blmorefx_tab,mt_nop-blmorefx_tab
dc.w mt_nop-blmorefx_tab,mt_nop-blmorefx_tab
dc.w mt_nop-blmorefx_tab
dc.w mt_posjump-blmorefx_tab ; $B
dc.w mt_nop-blmorefx_tab
dc.w mt_patternbrk-blmorefx_tab ; $D
dc.w blocked_e_cmds-blmorefx_tab
dc.w mt_setspeed-blmorefx_tab ; $F
endc ; !MINIMAL
mt_arpeggio:
; cmd 0 x y (x = first arpeggio offset, y = second arpeggio offset)
; d4 = xy
moveq #0,d0
move.b mt_Counter(a4),d0
move.b arptab(pc,d0.w),d0
beq mt_pernop ; step 0, just use normal period
bmi .1
; step 1, arpeggio by left nibble
lsr.b #4,d4
bra .2
; step 2, arpeggio by right nibble
.1: and.w #$000f,d4
; offset current note
.2: add.w d4,d4
add.w n_noteoff(a2),d4
cmp.w #2*36,d4
bhs .4
; set period with arpeggio offset from note table
move.l n_pertab(a2),a0
move.w (a0,d4.w),AUDPER(a5)
.4: rts
arptab:
dc.b 0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0
dc.b 1,-1,0,1,-1,0,1,-1,0,1,-1,0,1,-1,0,1
mt_fineportaup:
; cmd E 1 x (subtract x from period)
; d0 = x
tst.b mt_Counter(a4)
beq do_porta_up
rts
mt_portaup:
; cmd 1 x x (subtract xx from period)
; d4 = xx
move.w d4,d0
do_porta_up:
move.w n_period(a2),d1
sub.w d0,d1
cmp.w #113,d1
bge .1
moveq #113,d1
.1: move.w d1,n_period(a2)
move.w d1,AUDPER(a5)
rts
mt_fineportadn:
; cmd E 2 x (add x to period)
; d0 = x
tst.b mt_Counter(a4)
beq do_porta_down
rts
mt_portadown:
; cmd 2 x x (add xx to period)
; d4 = xx
move.w d4,d0
do_porta_down:
move.w n_period(a2),d1
add.w d0,d1
cmp.w #856,d1
bls .1
move.w #856,d1
.1: move.w d1,n_period(a2)
move.w d1,AUDPER(a5)
rts
mt_toneporta:
; cmd 3 x y (xy = tone portamento speed)
; d4 = xy
tst.b d4
beq mt_toneporta_nc
move.w d4,n_toneportspeed(a2)
move.b d7,n_cmdlo(a2)
mt_toneporta_nc:
move.w n_wantedperiod(a2),d1
beq .6
move.w n_toneportspeed(a2),d0
move.w n_period(a2),d2
cmp.w d1,d2
blo .2
; tone porta up
sub.w d0,d2
cmp.w d1,d2
bgt .3
move.w d1,d2
move.w d7,n_wantedperiod(a2)
bra .3
; tone porta down
.2: add.w d0,d2
cmp.w d1,d2
blt .3
move.w d1,d2
move.w d7,n_wantedperiod(a2)
.3: move.w d2,n_period(a2)
tst.b n_gliss(a2)
beq .5
; glissando: find nearest note for new period
move.l n_pertab(a2),a0
moveq #36-1,d0
moveq #-2,d1
.4: addq.w #2,d1
cmp.w (a0)+,d2
dbhs d0,.4
move.w d1,n_noteoff(a2) ; @@@ needed?
move.w -(a0),d2
.5: move.w d2,AUDPER(a5)
.6 rts
mt_vibrato:
; cmd 4 x y (x = speed, y = amplitude)
; d4 = xy
moveq #$0f,d2
and.b d4,d2
beq .1
move.b d2,n_vibratoamp(a2)
bra .2
.1: move.b n_vibratoamp(a2),d2
.2: lsr.b #4,d4
beq .3
move.b d4,n_vibratospd(a2)
bra mt_vibrato_nc
.3: move.b n_vibratospd(a2),d4
mt_vibrato_nc:
; calculate vibrato table offset: 64 * amplitude + (pos & 63)
lsl.w #6,d2
moveq #63,d0
and.b n_vibratopos(a2),d0
add.w d0,d2
ifne ENABLE_SAWRECT
; select vibrato waveform
moveq #3,d1
and.b n_vibratoctrl(a2),d1
beq .6
subq.b #1,d1
beq .5
; ctrl 2 & 3 select a rectangle vibrato
lea mt_VibratoRectTable(pc),a0
bra .9
; ctrl 1 selects a sawtooth vibrato
.5: lea mt_VibratoSawTable(pc),a0
bra .9
endc ; ENABLE_SAWRECT
; ctrl 0 selects a sine vibrato
.6: lea mt_VibratoSineTable(pc),a0
; add vibrato-offset to period
.9: move.b (a0,d2.w),d0
ext.w d0
add.w n_period(a2),d0
move.w d0,AUDPER(a5)
; increase vibratopos by speed
add.b d4,n_vibratopos(a2)
rts
mt_tonevolslide:
; cmd 5 x y (x = volume-up, y = volume-down)
; d4 = xy
pea mt_volumeslide(pc)
bra mt_toneporta_nc
mt_vibrvolslide:
; cmd 6 x y (x = volume-up, y = volume-down)
; d4 = xy
move.w d4,d3
move.b n_vibratoamp(a2),d2
move.b n_vibratospd(a2),d4
bsr mt_vibrato_nc
move.w d3,d4
bra mt_volumeslide
mt_tremolo:
; cmd 7 x y (x = speed, y = amplitude)
; d4 = xy
moveq #$0f,d2
and.b d4,d2
beq .1
move.b d2,n_tremoloamp(a2)
bra .2
.1: move.b n_tremoloamp(a2),d2
.2: lsr.b #4,d4
beq .3
move.b d4,n_tremolospd(a2)
bra .4
.3: move.b n_tremolospd(a2),d4
; calculate tremolo table offset: 64 * amplitude + (pos & 63)
.4: lsl.w #6,d2
moveq #63,d0
and.b n_tremolopos(a2),d0
add.w d0,d2
ifne ENABLE_SAWRECT
; select tremolo waveform
moveq #3,d1
and.b n_tremoloctrl(a2),d1
beq .6
subq.b #1,d1
beq .5
; ctrl 2 & 3 select a rectangle tremolo
lea mt_VibratoRectTable(pc),a0
bra .9
; ctrl 1 selects a sawtooth tremolo
.5: lea mt_VibratoSawTable(pc),a0
bra .9
endc ; ENABLE_SAWRECT
; ctrl 0 selects a sine tremolo
.6: lea mt_VibratoSineTable(pc),a0
; add tremolo-offset to volume
.9: move.w n_volume(a2),d0
add.b (a0,d2.w),d0
bpl .10
moveq #0,d0
.10: cmp.w #64,d0
bls .11
moveq #64,d0
.11: move.w n_period(a2),AUDPER(a5)
ifeq MINIMAL
move.l mt_MasterVolTab(a4),a0
move.b (a0,d0.w),d0
and.b n_enable(a2),d0
endc
move.w d0,AUDVOL(a5)
ifd VOL0_UNLOOPS
bne .12
move.b d7,n_looped(a2)
endc
; increase tremolopos by speed
.12: add.b d4,n_tremolopos(a2)
rts
mt_volumeslide:
; cmd A x y (x = volume-up, y = volume-down)
; d4 = xy
move.w n_volume(a2),d0
moveq #$0f,d1
and.b d4,d1
lsr.b #4,d4
beq vol_slide_down
; slide up, until 64
add.b d4,d0
vol_slide_up:
cmp.b #64,d0
bls set_pervol
moveq #64,d0
bra set_pervol
; slide down, until 0
vol_slide_down:
sub.b d1,d0
bpl set_pervol
moveq #0,d0
set_pervol:
move.w n_period(a2),AUDPER(a5)
set_vol:
move.w d0,n_volume(a2)
ifeq MINIMAL
move.l mt_MasterVolTab(a4),a0
move.b (a0,d0.w),d0
and.b n_enable(a2),d0
endc
move.w d0,AUDVOL(a5)
ifd VOL0_UNLOOPS
bne .1
move.b d7,n_looped(a2)
endc
.1: rts
mt_posjump:
; cmd B x y (xy = new song position)
; d4 = xy
move.b d4,d0
subq.b #1,d0
move.b d0,mt_SongPos(a4)
jump_pos0:
move.w d7,mt_PBreakPos(a4)
st mt_PosJumpFlag(a4)
rts
mt_volchange:
; cmd C x y (xy = new volume)
; d4 = xy
move.w d4,d0
cmp.w #64,d0
bls set_vol
moveq #64,d0
bra set_vol
mt_patternbrk:
; cmd D x y (xy = break pos in decimal)
; d4 = xy
moveq #$0f,d0
and.w d4,d0
move.w d4,d1
lsr.w #4,d1
add.b mult10tab(pc,d1.w),d0
cmp.b #63,d0
bhi jump_pos0
lsl.w #4,d0
move.w d0,mt_PBreakPos(a4)
st mt_PosJumpFlag(a4)
rts
mult10tab:
dc.b 0,10,20,30,40,50,60,70,80,90,0,0,0,0,0,0
mt_setspeed:
; cmd F x y (xy<$20 new speed, xy>=$20 new tempo)
; d4 = xy
ifeq VBLANK_MUSIC
cmp.b #$20,d4
bhs .1
endc
move.b d4,mt_Speed(a4)
beq _mt_end
rts
ifeq VBLANK_MUSIC
; set tempo (CIA only)
.1: and.w #$00ff,d4
move.l mt_timerval(a4),d0
divu d4,d0
move.b d0,CIAB+CIATALO
lsr.w #8,d0
move.b d0,CIAB+CIATAHI
rts
endc
mt_e_cmds:
; cmd E x y (x=command, y=argument)
; d4 = xy
moveq #$0f,d0
and.w d4,d0 ; pass E-cmd argument in d0
move.w d4,d1
lsr.w #4,d1
add.w d1,d1
move.w ecmd_tab(pc,d1.w),d1
jmp ecmd_tab(pc,d1.w)
ecmd_tab:
dc.w mt_filter-ecmd_tab
dc.w mt_fineportaup-ecmd_tab
dc.w mt_fineportadn-ecmd_tab
dc.w mt_glissctrl-ecmd_tab
dc.w mt_vibratoctrl-ecmd_tab
dc.w mt_finetune-ecmd_tab
dc.w mt_jumploop-ecmd_tab
dc.w mt_tremoctrl-ecmd_tab
dc.w mt_e8-ecmd_tab
dc.w mt_retrignote-ecmd_tab
dc.w mt_volfineup-ecmd_tab
dc.w mt_volfinedn-ecmd_tab
dc.w mt_notecut-ecmd_tab
dc.w mt_notedelay-ecmd_tab
dc.w mt_patterndelay-ecmd_tab
dc.w mt_funk-ecmd_tab
blocked_e_cmds:
; cmd E x y (x=command, y=argument)
; d4 = xy
moveq #$0f,d0
and.w d4,d0 ; pass E-cmd argument in d0
move.w d4,d1
lsr.w #4,d1
add.w d1,d1
move.w blecmd_tab(pc,d1.w),d1
jmp blecmd_tab(pc,d1.w)
blecmd_tab:
dc.w mt_filter-blecmd_tab
dc.w mt_rts-blecmd_tab
dc.w mt_rts-blecmd_tab
dc.w mt_glissctrl-blecmd_tab
dc.w mt_vibratoctrl-blecmd_tab
dc.w mt_finetune-blecmd_tab
dc.w mt_jumploop-blecmd_tab
dc.w mt_tremoctrl-blecmd_tab
dc.w mt_e8-blecmd_tab
dc.w mt_rts-blecmd_tab
dc.w mt_rts-blecmd_tab
dc.w mt_rts-blecmd_tab
dc.w mt_rts-blecmd_tab
dc.w mt_rts-blecmd_tab
dc.w mt_patterndelay-blecmd_tab
dc.w mt_rts-blecmd_tab
mt_filter:
; cmd E 0 x (x=1 disable, x=0 enable)
; d0 = x
lsr.b #1,d0
bcs .1
bclr #1,CIAA+CIAPRA
rts
.1: bset #1,CIAA+CIAPRA
mt_rts:
rts
mt_glissctrl:
; cmd E 3 x (x gliss)
; d0 = x
move.b d0,n_gliss(a2)
rts
mt_vibratoctrl:
; cmd E 4 x (x = vibrato)
; d0 = x
move.b d0,n_vibratoctrl(a2)
rts
mt_finetune:
; cmd E 5 x (x = finetune)
; d0 = x
lea mt_PerFineTune(pc),a0
add.w d0,d0
add.w (a0,d0.w),a0
move.l a0,n_pertab(a2)
cmp.w #2*8,d0
shs n_minusft(a2)
rts
mt_jumploop:
; cmd E 6 x (x=0 loop start, else loop count)
; d0 = x
tst.b mt_Counter(a4)
bne .4
.1: tst.b d0
beq .3 ; set start
; otherwise we are at the end of the loop
subq.b #1,n_loopcount(a2)
beq .4 ; loop finished
bpl .2
; initialize loop counter
move.b d0,n_loopcount(a2)
; jump back to start of loop
.2: move.w n_pattpos(a2),mt_PBreakPos(a4)
st mt_PBreakFlag(a4)
rts
; remember start of loop position
.3: move.w mt_PatternPos(a4),n_pattpos(a2)
.4: rts
mt_tremoctrl:
; cmd E 7 x (x = tremolo)
; d0 = x
move.b d0,n_tremoloctrl(a2)
rts
mt_e8:
; cmd E 8 x (x = trigger value)
; d0 = x
move.b d0,mt_E8Trigger(a4)
rts
mt_retrignote:
; cmd E 9 x (x = retrigger count)
; d0 = x
tst.b d0
beq .1
; set new retrigger count when Counter=0
tst.b mt_Counter(a4)
bne .2
move.b d0,n_retrigcount(a2)
; avoid double retrigger, when Counter=0 and a note was set
move.w #$0fff,d2
and.w (a2),d2
beq do_retrigger
.1: rts
; check if retrigger count is reached
.2: subq.b #1,n_retrigcount(a2)
bne .1
move.b d0,n_retrigcount(a2) ; reset
do_retrigger:
; DMA off, set sample pointer and length
move.w n_dmabit(a2),d0
move.w d0,DMACON(a6)
move.l n_start(a2),AUDLC(a5)
move.w n_length(a2),AUDLEN(a5)
lea mt_dmaon(pc),a0
or.w d0,(a0)
rts
mt_volfineup:
; cmd E A x (x = volume add)
; d0 = x
tst.b mt_Counter(a4)
beq .1
rts
.1: add.w n_volume(a2),d0
bra vol_slide_up
mt_volfinedn:
; cmd E B x (x = volume sub)
; d0 = x
tst.b mt_Counter(a4)
beq .1
rts
.1: move.b d0,d1
move.w n_volume(a2),d0
bra vol_slide_down
mt_notecut:
; cmd E C x (x = counter to cut at)
; d0 = x
cmp.b mt_Counter(a4),d0
bne .1
move.w d7,n_volume(a2)
move.w d7,AUDVOL(a5)
ifd VOL0_UNLOOPS
move.b d7,n_looped(a2)
endc
.1: rts
mt_notedelay:
; cmd E D x (x = counter to retrigger at)
; d0 = x
cmp.b mt_Counter(a4),d0
bne .1
tst.w (a2) ; trigger note when given
bne .2
.1: rts
.2: move.w n_period(a2),AUDPER(a5)
bra do_retrigger
mt_patterndelay:
; cmd E E x (x = delay count)
; d0 = x
tst.b mt_Counter(a4)
bne .1
tst.b mt_PattDelTime2(a4)
bne .1
addq.b #1,d0
move.b d0,mt_PattDelTime(a4)
.1: rts
mt_funk:
; cmd E F x (x = funk speed)
; d0 = x
tst.b mt_Counter(a4)
bne .1
move.w d0,n_funk(a2)
bne mt_updatefunk
.1: rts
mt_updatefunk:
; d0 = funk speed
move.b mt_FunkTable(pc,d0.w),d0
add.b d0,n_funkoffset(a2)
bpl .2
move.b d7,n_funkoffset(a2)
move.l n_loopstart(a2),d0
moveq #0,d1
move.w n_replen(a2),d1
add.l d1,d1
add.l d0,d1
move.l n_wavestart(a2),a0
addq.l #1,a0
cmp.l d1,a0
blo .1
move.l d0,a0
.1: move.l a0,n_wavestart(a2)
not.b (a0)
.2: rts
mt_FunkTable:
dc.b 0,5,6,7,8,10,11,13,16,19,22,26,32,43,64,128
mt_VibratoSineTable:
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1
dc.b 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0
dc.b 0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
dc.b -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0
dc.b 0,0,0,1,1,1,2,2,2,3,3,3,3,3,3,3
dc.b 3,3,3,3,3,3,3,3,2,2,2,1,1,1,0,0
dc.b 0,0,0,-1,-1,-1,-2,-2,-2,-3,-3,-3,-3,-3,-3,-3
dc.b -3,-3,-3,-3,-3,-3,-3,-3,-2,-2,-2,-1,-1,-1,0,0
dc.b 0,0,1,1,2,2,3,3,4,4,4,5,5,5,5,5
dc.b 5,5,5,5,5,5,4,4,4,3,3,2,2,1,1,0
dc.b 0,0,-1,-1,-2,-2,-3,-3,-4,-4,-4,-5,-5,-5,-5,-5
dc.b -5,-5,-5,-5,-5,-5,-4,-4,-4,-3,-3,-2,-2,-1,-1,0
dc.b 0,0,1,2,3,3,4,5,5,6,6,7,7,7,7,7
dc.b 7,7,7,7,7,7,6,6,5,5,4,3,3,2,1,0
dc.b 0,0,-1,-2,-3,-3,-4,-5,-5,-6,-6,-7,-7,-7,-7,-7
dc.b -7,-7,-7,-7,-7,-7,-6,-6,-5,-5,-4,-3,-3,-2,-1,0
dc.b 0,0,1,2,3,4,5,6,7,7,8,8,9,9,9,9
dc.b 9,9,9,9,9,8,8,7,7,6,5,4,3,2,1,0
dc.b 0,0,-1,-2,-3,-4,-5,-6,-7,-7,-8,-8,-9,-9,-9,-9
dc.b -9,-9,-9,-9,-9,-8,-8,-7,-7,-6,-5,-4,-3,-2,-1,0
dc.b 0,1,2,3,4,5,6,7,8,9,9,10,11,11,11,11
dc.b 11,11,11,11,11,10,9,9,8,7,6,5,4,3,2,1
dc.b 0,-1,-2,-3,-4,-5,-6,-7,-8,-9,-9,-10,-11,-11,-11,-11
dc.b -11,-11,-11,-11,-11,-10,-9,-9,-8,-7,-6,-5,-4,-3,-2,-1
dc.b 0,1,2,4,5,6,7,8,9,10,11,12,12,13,13,13
dc.b 13,13,13,13,12,12,11,10,9,8,7,6,5,4,2,1
dc.b 0,-1,-2,-4,-5,-6,-7,-8,-9,-10,-11,-12,-12,-13,-13,-13
dc.b -13,-13,-13,-13,-12,-12,-11,-10,-9,-8,-7,-6,-5,-4,-2,-1
dc.b 0,1,3,4,6,7,8,10,11,12,13,14,14,15,15,15
dc.b 15,15,15,15,14,14,13,12,11,10,8,7,6,4,3,1
dc.b 0,-1,-3,-4,-6,-7,-8,-10,-11,-12,-13,-14,-14,-15,-15,-15
dc.b -15,-15,-15,-15,-14,-14,-13,-12,-11,-10,-8,-7,-6,-4,-3,-1
dc.b 0,1,3,5,6,8,9,11,12,13,14,15,16,17,17,17
dc.b 17,17,17,17,16,15,14,13,12,11,9,8,6,5,3,1
dc.b 0,-1,-3,-5,-6,-8,-9,-11,-12,-13,-14,-15,-16,-17,-17,-17
dc.b -17,-17,-17,-17,-16,-15,-14,-13,-12,-11,-9,-8,-6,-5,-3,-1
dc.b 0,1,3,5,7,9,11,12,14,15,16,17,18,19,19,19
dc.b 19,19,19,19,18,17,16,15,14,12,11,9,7,5,3,1
dc.b 0,-1,-3,-5,-7,-9,-11,-12,-14,-15,-16,-17,-18,-19,-19,-19
dc.b -19,-19,-19,-19,-18,-17,-16,-15,-14,-12,-11,-9,-7,-5,-3,-1
dc.b 0,2,4,6,8,10,12,13,15,16,18,19,20,20,21,21
dc.b 21,21,21,20,20,19,18,16,15,13,12,10,8,6,4,2
dc.b 0,-2,-4,-6,-8,-10,-12,-13,-15,-16,-18,-19,-20,-20,-21,-21
dc.b -21,-21,-21,-20,-20,-19,-18,-16,-15,-13,-12,-10,-8,-6,-4,-2
dc.b 0,2,4,6,9,11,13,15,16,18,19,21,22,22,23,23
dc.b 23,23,23,22,22,21,19,18,16,15,13,11,9,6,4,2
dc.b 0,-2,-4,-6,-9,-11,-13,-15,-16,-18,-19,-21,-22,-22,-23,-23
dc.b -23,-23,-23,-22,-22,-21,-19,-18,-16,-15,-13,-11,-9,-6,-4,-2
dc.b 0,2,4,7,9,12,14,16,18,20,21,22,23,24,25,25
dc.b 25,25,25,24,23,22,21,20,18,16,14,12,9,7,4,2
dc.b 0,-2,-4,-7,-9,-12,-14,-16,-18,-20,-21,-22,-23,-24,-25,-25
dc.b -25,-25,-25,-24,-23,-22,-21,-20,-18,-16,-14,-12,-9,-7,-4,-2
dc.b 0,2,5,8,10,13,15,17,19,21,23,24,25,26,27,27
dc.b 27,27,27,26,25,24,23,21,19,17,15,13,10,8,5,2
dc.b 0,-2,-5,-8,-10,-13,-15,-17,-19,-21,-23,-24,-25,-26,-27,-27
dc.b -27,-27,-27,-26,-25,-24,-23,-21,-19,-17,-15,-13,-10,-8,-5,-2
dc.b 0,2,5,8,11,14,16,18,21,23,24,26,27,28,29,29
dc.b 29,29,29,28,27,26,24,23,21,18,16,14,11,8,5,2
dc.b 0,-2,-5,-8,-11,-14,-16,-18,-21,-23,-24,-26,-27,-28,-29,-29
dc.b -29,-29,-29,-28,-27,-26,-24,-23,-21,-18,-16,-14,-11,-8,-5,-2
ifne ENABLE_SAWRECT
mt_VibratoSawTable:
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
dc.b -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1
dc.b 2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3
dc.b -3,-3,-3,-3,-3,-3,-3,-3,-2,-2,-2,-2,-2,-2,-2,-2
dc.b -1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,1,1,1,1,1,2,2,2,2,2
dc.b 3,3,3,3,3,3,4,4,4,4,4,5,5,5,5,5
dc.b -5,-5,-5,-5,-5,-5,-4,-4,-4,-4,-4,-3,-3,-3,-3,-3
dc.b -2,-2,-2,-2,-2,-2,-1,-1,-1,-1,-1,0,0,0,0,0
dc.b 0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3
dc.b 4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7
dc.b -7,-7,-7,-7,-6,-6,-6,-6,-5,-5,-5,-5,-4,-4,-4,-4
dc.b -3,-3,-3,-3,-2,-2,-2,-2,-1,-1,-1,-1,0,0,0,0
dc.b 0,0,0,0,1,1,1,2,2,2,3,3,3,4,4,4
dc.b 5,5,5,5,6,6,6,7,7,7,8,8,8,9,9,9
dc.b -9,-9,-9,-9,-8,-8,-8,-7,-7,-7,-6,-6,-6,-5,-5,-5
dc.b -4,-4,-4,-4,-3,-3,-3,-2,-2,-2,-1,-1,-1,0,0,0
dc.b 0,0,0,1,1,1,2,2,3,3,3,4,4,4,5,5
dc.b 6,6,6,7,7,7,8,8,9,9,9,10,10,10,11,11
dc.b -11,-11,-11,-10,-10,-10,-9,-9,-8,-8,-8,-7,-7,-7,-6,-6
dc.b -5,-5,-5,-4,-4,-4,-3,-3,-2,-2,-2,-1,-1,-1,0,0
dc.b 0,0,0,1,1,2,2,3,3,3,4,4,5,5,6,6
dc.b 7,7,7,8,8,9,9,10,10,10,11,11,12,12,13,13
dc.b -13,-13,-13,-12,-12,-11,-11,-10,-10,-10,-9,-9,-8,-8,-7,-7
dc.b -6,-6,-6,-5,-5,-4,-4,-3,-3,-3,-2,-2,-1,-1,0,0
dc.b 0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7
dc.b 8,8,9,9,10,10,11,11,12,12,13,13,14,14,15,15
dc.b -15,-15,-14,-14,-13,-13,-12,-12,-11,-11,-10,-10,-9,-9,-8,-8
dc.b -7,-7,-6,-6,-5,-5,-4,-4,-3,-3,-2,-2,-1,-1,0,0
dc.b 0,0,1,1,2,2,3,3,4,5,5,6,6,7,7,8
dc.b 9,9,10,10,11,11,12,12,13,14,14,15,15,16,16,17
dc.b -17,-17,-16,-16,-15,-15,-14,-13,-13,-12,-12,-11,-11,-10,-10,-9
dc.b -8,-8,-7,-7,-6,-6,-5,-4,-4,-3,-3,-2,-2,-1,-1,0
dc.b 0,0,1,1,2,3,3,4,5,5,6,6,7,8,8,9
dc.b 10,10,11,11,12,13,13,14,15,15,16,16,17,18,18,19
dc.b -19,-19,-18,-18,-17,-16,-16,-15,-14,-14,-13,-13,-12,-11,-11,-10
dc.b -9,-9,-8,-8,-7,-6,-6,-5,-4,-4,-3,-3,-2,-1,-1,0
dc.b 0,0,1,2,2,3,4,4,5,6,6,7,8,8,9,10
dc.b 11,11,12,13,13,14,15,15,16,17,17,18,19,19,20,21
dc.b -21,-21,-20,-19,-19,-18,-17,-17,-16,-15,-15,-14,-13,-12,-12,-11
dc.b -10,-10,-9,-8,-8,-7,-6,-6,-5,-4,-4,-3,-2,-1,-1,0
dc.b 0,0,1,2,3,3,4,5,6,6,7,8,9,9,10,11
dc.b 12,12,13,14,15,15,16,17,18,18,19,20,21,21,22,23
dc.b -23,-23,-22,-21,-20,-20,-19,-18,-17,-17,-16,-15,-14,-14,-13,-12
dc.b -11,-11,-10,-9,-8,-8,-7,-6,-5,-5,-4,-3,-2,-2,-1,0
dc.b 0,0,1,2,3,4,4,5,6,7,8,8,9,10,11,12
dc.b 13,13,14,15,16,17,17,18,19,20,21,21,22,23,24,25
dc.b -25,-25,-24,-23,-22,-21,-21,-20,-19,-18,-17,-16,-16,-15,-14,-13
dc.b -12,-12,-11,-10,-9,-8,-8,-7,-6,-5,-4,-3,-3,-2,-1,0
dc.b 0,0,1,2,3,4,5,6,7,7,8,9,10,11,12,13
dc.b 14,14,15,16,17,18,19,20,21,21,22,23,24,25,26,27
dc.b -27,-27,-26,-25,-24,-23,-22,-21,-20,-20,-19,-18,-17,-16,-15,-14
dc.b -13,-13,-12,-11,-10,-9,-8,-7,-6,-6,-5,-4,-3,-2,-1,0
dc.b 0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14
dc.b 15,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29
dc.b -29,-28,-28,-27,-26,-25,-24,-23,-22,-21,-20,-19,-18,-17,-16,-15
dc.b -14,-13,-13,-12,-11,-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,0
mt_VibratoRectTable:
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
dc.b 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
dc.b -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
dc.b -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
dc.b 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3
dc.b 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3
dc.b -3,-3,-3,-3,-3,-3,-3,-3,-3,-3,-3,-3,-3,-3,-3,-3
dc.b -3,-3,-3,-3,-3,-3,-3,-3,-3,-3,-3,-3,-3,-3,-3,-3
dc.b 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5
dc.b 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5
dc.b -5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5
dc.b -5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5,-5
dc.b 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
dc.b 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
dc.b -7,-7,-7,-7,-7,-7,-7,-7,-7,-7,-7,-7,-7,-7,-7,-7
dc.b -7,-7,-7,-7,-7,-7,-7,-7,-7,-7,-7,-7,-7,-7,-7,-7
dc.b 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9
dc.b 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9
dc.b -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9
dc.b -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9
dc.b 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11
dc.b 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11
dc.b -11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11
dc.b -11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11,-11
dc.b 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13
dc.b 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13
dc.b -13,-13,-13,-13,-13,-13,-13,-13,-13,-13,-13,-13,-13,-13,-13,-13
dc.b -13,-13,-13,-13,-13,-13,-13,-13,-13,-13,-13,-13,-13,-13,-13,-13
dc.b 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15
dc.b 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15
dc.b -15,-15,-15,-15,-15,-15,-15,-15,-15,-15,-15,-15,-15,-15,-15,-15
dc.b -15,-15,-15,-15,-15,-15,-15,-15,-15,-15,-15,-15,-15,-15,-15,-15
dc.b 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17
dc.b 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17
dc.b -17,-17,-17,-17,-17,-17,-17,-17,-17,-17,-17,-17,-17,-17,-17,-17
dc.b -17,-17,-17,-17,-17,-17,-17,-17,-17,-17,-17,-17,-17,-17,-17,-17
dc.b 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19
dc.b 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19
dc.b -19,-19,-19,-19,-19,-19,-19,-19,-19,-19,-19,-19,-19,-19,-19,-19
dc.b -19,-19,-19,-19,-19,-19,-19,-19,-19,-19,-19,-19,-19,-19,-19,-19
dc.b 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21
dc.b 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21
dc.b -21,-21,-21,-21,-21,-21,-21,-21,-21,-21,-21,-21,-21,-21,-21,-21
dc.b -21,-21,-21,-21,-21,-21,-21,-21,-21,-21,-21,-21,-21,-21,-21,-21
dc.b 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23
dc.b 23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23
dc.b -23,-23,-23,-23,-23,-23,-23,-23,-23,-23,-23,-23,-23,-23,-23,-23
dc.b -23,-23,-23,-23,-23,-23,-23,-23,-23,-23,-23,-23,-23,-23,-23,-23
dc.b 25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
dc.b 25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25
dc.b -25,-25,-25,-25,-25,-25,-25,-25,-25,-25,-25,-25,-25,-25,-25,-25
dc.b -25,-25,-25,-25,-25,-25,-25,-25,-25,-25,-25,-25,-25,-25,-25,-25
dc.b 27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27
dc.b 27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27
dc.b -27,-27,-27,-27,-27,-27,-27,-27,-27,-27,-27,-27,-27,-27,-27,-27
dc.b -27,-27,-27,-27,-27,-27,-27,-27,-27,-27,-27,-27,-27,-27,-27,-27
dc.b 29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29
dc.b 29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29
dc.b -29,-29,-29,-29,-29,-29,-29,-29,-29,-29,-29,-29,-29,-29,-29,-29
dc.b -29,-29,-29,-29,-29,-29,-29,-29,-29,-29,-29,-29,-29,-29,-29,-29
endc ; ENABLE_SAWRECT
mt_PerFineTune:
dc.w mt_Tuning0-mt_PerFineTune,mt_Tuning1-mt_PerFineTune
dc.w mt_Tuning2-mt_PerFineTune,mt_Tuning3-mt_PerFineTune
dc.w mt_Tuning4-mt_PerFineTune,mt_Tuning5-mt_PerFineTune
dc.w mt_Tuning6-mt_PerFineTune,mt_Tuning7-mt_PerFineTune
dc.w mt_TuningM8-mt_PerFineTune,mt_TuningM7-mt_PerFineTune
dc.w mt_TuningM6-mt_PerFineTune,mt_TuningM5-mt_PerFineTune
dc.w mt_TuningM4-mt_PerFineTune,mt_TuningM3-mt_PerFineTune
dc.w mt_TuningM2-mt_PerFineTune,mt_TuningM1-mt_PerFineTune
mt_PeriodTable:
mt_Tuning0: ; Tuning 0, Normal c-1 - b3
dc.w 856,808,762,720,678,640,604,570,538,508,480,453
dc.w 428,404,381,360,339,320,302,285,269,254,240,226
dc.w 214,202,190,180,170,160,151,143,135,127,120,113
mt_Tuning1:
dc.w 850,802,757,715,674,637,601,567,535,505,477,450
dc.w 425,401,379,357,337,318,300,284,268,253,239,225
dc.w 213,201,189,179,169,159,150,142,134,126,119,113
mt_Tuning2:
dc.w 844,796,752,709,670,632,597,563,532,502,474,447
dc.w 422,398,376,355,335,316,298,282,266,251,237,224
dc.w 211,199,188,177,167,158,149,141,133,125,118,112
mt_Tuning3:
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
mt_Tuning4:
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
mt_Tuning5:
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
mt_Tuning6:
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
mt_Tuning7:
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
mt_TuningM8:
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
mt_TuningM7:
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
mt_TuningM6:
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
mt_TuningM5:
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
mt_TuningM4:
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
mt_TuningM3:
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
mt_TuningM2:
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
mt_TuningM1:
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
ifeq MINIMAL
MasterVolTab0:
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0
MasterVolTab1:
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 1
MasterVolTab2:
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
dc.b 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
dc.b 2
MasterVolTab3:
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1
dc.b 1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2
dc.b 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
dc.b 3
MasterVolTab4:
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dc.b 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
dc.b 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
dc.b 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3
dc.b 4
MasterVolTab5:
dc.b 0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1
dc.b 1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2
dc.b 2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3
dc.b 3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4
dc.b 5
MasterVolTab6:
dc.b 0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1
dc.b 1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2
dc.b 3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4
dc.b 4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5
dc.b 6
MasterVolTab7:
dc.b 0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1
dc.b 1,1,1,2,2,2,2,2,2,2,2,2,3,3,3,3
dc.b 3,3,3,3,3,4,4,4,4,4,4,4,4,4,5,5
dc.b 5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6
dc.b 7
MasterVolTab8:
dc.b 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1
dc.b 2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3
dc.b 4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5
dc.b 6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7
dc.b 8
MasterVolTab9:
dc.b 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,2
dc.b 2,2,2,2,2,2,3,3,3,3,3,3,3,4,4,4
dc.b 4,4,4,4,5,5,5,5,5,5,5,6,6,6,6,6
dc.b 6,6,7,7,7,7,7,7,7,8,8,8,8,8,8,8
dc.b 9
MasterVolTab10:
dc.b 0,0,0,0,0,0,0,1,1,1,1,1,1,2,2,2
dc.b 2,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4
dc.b 5,5,5,5,5,5,5,6,6,6,6,6,6,7,7,7
dc.b 7,7,7,7,8,8,8,8,8,8,9,9,9,9,9,9
dc.b 10
MasterVolTab11:
dc.b 0,0,0,0,0,0,1,1,1,1,1,1,2,2,2,2
dc.b 2,2,3,3,3,3,3,3,4,4,4,4,4,4,5,5
dc.b 5,5,5,6,6,6,6,6,6,7,7,7,7,7,7,8
dc.b 8,8,8,8,8,9,9,9,9,9,9,10,10,10,10,10
dc.b 11
MasterVolTab12:
dc.b 0,0,0,0,0,0,1,1,1,1,1,2,2,2,2,2
dc.b 3,3,3,3,3,3,4,4,4,4,4,5,5,5,5,5
dc.b 6,6,6,6,6,6,7,7,7,7,7,8,8,8,8,8
dc.b 9,9,9,9,9,9,10,10,10,10,10,11,11,11,11,11
dc.b 12
MasterVolTab13:
dc.b 0,0,0,0,0,1,1,1,1,1,2,2,2,2,2,3
dc.b 3,3,3,3,4,4,4,4,4,5,5,5,5,5,6,6
dc.b 6,6,6,7,7,7,7,7,8,8,8,8,8,9,9,9
dc.b 9,9,10,10,10,10,10,11,11,11,11,11,12,12,12,12
dc.b 13
MasterVolTab14:
dc.b 0,0,0,0,0,1,1,1,1,1,2,2,2,2,3,3
dc.b 3,3,3,4,4,4,4,5,5,5,5,5,6,6,6,6
dc.b 7,7,7,7,7,8,8,8,8,8,9,9,9,9,10,10
dc.b 10,10,10,11,11,11,11,12,12,12,12,12,13,13,13,13
dc.b 14
MasterVolTab15:
dc.b 0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3
dc.b 3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7
dc.b 7,7,7,8,8,8,8,9,9,9,9,10,10,10,10,11
dc.b 11,11,11,11,12,12,12,12,13,13,13,13,14,14,14,14
dc.b 15
MasterVolTab16:
dc.b 0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3
dc.b 4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7
dc.b 8,8,8,8,9,9,9,9,10,10,10,10,11,11,11,11
dc.b 12,12,12,12,13,13,13,13,14,14,14,14,15,15,15,15
dc.b 16
MasterVolTab17:
dc.b 0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3
dc.b 4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,8
dc.b 8,8,9,9,9,9,10,10,10,10,11,11,11,11,12,12
dc.b 12,13,13,13,13,14,14,14,14,15,15,15,15,16,16,16
dc.b 17
MasterVolTab18:
dc.b 0,0,0,0,1,1,1,1,2,2,2,3,3,3,3,4
dc.b 4,4,5,5,5,5,6,6,6,7,7,7,7,8,8,8
dc.b 9,9,9,9,10,10,10,10,11,11,11,12,12,12,12,13
dc.b 13,13,14,14,14,14,15,15,15,16,16,16,16,17,17,17
dc.b 18
MasterVolTab19:
dc.b 0,0,0,0,1,1,1,2,2,2,2,3,3,3,4,4
dc.b 4,5,5,5,5,6,6,6,7,7,7,8,8,8,8,9
dc.b 9,9,10,10,10,10,11,11,11,12,12,12,13,13,13,13
dc.b 14,14,14,15,15,15,16,16,16,16,17,17,17,18,18,18
dc.b 19
MasterVolTab20:
dc.b 0,0,0,0,1,1,1,2,2,2,3,3,3,4,4,4
dc.b 5,5,5,5,6,6,6,7,7,7,8,8,8,9,9,9
dc.b 10,10,10,10,11,11,11,12,12,12,13,13,13,14,14,14
dc.b 15,15,15,15,16,16,16,17,17,17,18,18,18,19,19,19
dc.b 20
MasterVolTab21:
dc.b 0,0,0,0,1,1,1,2,2,2,3,3,3,4,4,4
dc.b 5,5,5,6,6,6,7,7,7,8,8,8,9,9,9,10
dc.b 10,10,11,11,11,12,12,12,13,13,13,14,14,14,15,15
dc.b 15,16,16,16,17,17,17,18,18,18,19,19,19,20,20,20
dc.b 21
MasterVolTab22:
dc.b 0,0,0,1,1,1,2,2,2,3,3,3,4,4,4,5
dc.b 5,5,6,6,6,7,7,7,8,8,8,9,9,9,10,10
dc.b 11,11,11,12,12,12,13,13,13,14,14,14,15,15,15,16
dc.b 16,16,17,17,17,18,18,18,19,19,19,20,20,20,21,21
dc.b 22
MasterVolTab23:
dc.b 0,0,0,1,1,1,2,2,2,3,3,3,4,4,5,5
dc.b 5,6,6,6,7,7,7,8,8,8,9,9,10,10,10,11
dc.b 11,11,12,12,12,13,13,14,14,14,15,15,15,16,16,16
dc.b 17,17,17,18,18,19,19,19,20,20,20,21,21,21,22,22
dc.b 23
MasterVolTab24:
dc.b 0,0,0,1,1,1,2,2,3,3,3,4,4,4,5,5
dc.b 6,6,6,7,7,7,8,8,9,9,9,10,10,10,11,11
dc.b 12,12,12,13,13,13,14,14,15,15,15,16,16,16,17,17
dc.b 18,18,18,19,19,19,20,20,21,21,21,22,22,22,23,23
dc.b 24
MasterVolTab25:
dc.b 0,0,0,1,1,1,2,2,3,3,3,4,4,5,5,5
dc.b 6,6,7,7,7,8,8,8,9,9,10,10,10,11,11,12
dc.b 12,12,13,13,14,14,14,15,15,16,16,16,17,17,17,18
dc.b 18,19,19,19,20,20,21,21,21,22,22,23,23,23,24,24
dc.b 25
MasterVolTab26:
dc.b 0,0,0,1,1,2,2,2,3,3,4,4,4,5,5,6
dc.b 6,6,7,7,8,8,8,9,9,10,10,10,11,11,12,12
dc.b 13,13,13,14,14,15,15,15,16,16,17,17,17,18,18,19
dc.b 19,19,20,20,21,21,21,22,22,23,23,23,24,24,25,25
dc.b 26
MasterVolTab27:
dc.b 0,0,0,1,1,2,2,2,3,3,4,4,5,5,5,6
dc.b 6,7,7,8,8,8,9,9,10,10,10,11,11,12,12,13
dc.b 13,13,14,14,15,15,16,16,16,17,17,18,18,18,19,19
dc.b 20,20,21,21,21,22,22,23,23,24,24,24,25,25,26,26
dc.b 27
MasterVolTab28:
dc.b 0,0,0,1,1,2,2,3,3,3,4,4,5,5,6,6
dc.b 7,7,7,8,8,9,9,10,10,10,11,11,12,12,13,13
dc.b 14,14,14,15,15,16,16,17,17,17,18,18,19,19,20,20
dc.b 21,21,21,22,22,23,23,24,24,24,25,25,26,26,27,27
dc.b 28
MasterVolTab29:
dc.b 0,0,0,1,1,2,2,3,3,4,4,4,5,5,6,6
dc.b 7,7,8,8,9,9,9,10,10,11,11,12,12,13,13,14
dc.b 14,14,15,15,16,16,17,17,18,18,19,19,19,20,20,21
dc.b 21,22,22,23,23,24,24,24,25,25,26,26,27,27,28,28
dc.b 29
MasterVolTab30:
dc.b 0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7
dc.b 7,7,8,8,9,9,10,10,11,11,12,12,13,13,14,14
dc.b 15,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22
dc.b 22,22,23,23,24,24,25,25,26,26,27,27,28,28,29,29
dc.b 30
MasterVolTab31:
dc.b 0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7
dc.b 7,8,8,9,9,10,10,11,11,12,12,13,13,14,14,15
dc.b 15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22
dc.b 23,23,24,24,25,25,26,26,27,27,28,28,29,29,30,30
dc.b 31
MasterVolTab32:
dc.b 0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7
dc.b 8,8,9,9,10,10,11,11,12,12,13,13,14,14,15,15
dc.b 16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23
dc.b 24,24,25,25,26,26,27,27,28,28,29,29,30,30,31,31
dc.b 32
MasterVolTab33:
dc.b 0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7
dc.b 8,8,9,9,10,10,11,11,12,12,13,13,14,14,15,15
dc.b 16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24
dc.b 24,25,25,26,26,27,27,28,28,29,29,30,30,31,31,32
dc.b 33
MasterVolTab34:
dc.b 0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7
dc.b 8,9,9,10,10,11,11,12,12,13,13,14,14,15,15,16
dc.b 17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24
dc.b 25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33
dc.b 34
MasterVolTab35:
dc.b 0,0,1,1,2,2,3,3,4,4,5,6,6,7,7,8
dc.b 8,9,9,10,10,11,12,12,13,13,14,14,15,15,16,16
dc.b 17,18,18,19,19,20,20,21,21,22,22,23,24,24,25,25
dc.b 26,26,27,27,28,28,29,30,30,31,31,32,32,33,33,34
dc.b 35
MasterVolTab36:
dc.b 0,0,1,1,2,2,3,3,4,5,5,6,6,7,7,8
dc.b 9,9,10,10,11,11,12,12,13,14,14,15,15,16,16,17
dc.b 18,18,19,19,20,20,21,21,22,23,23,24,24,25,25,26
dc.b 27,27,28,28,29,29,30,30,31,32,32,33,33,34,34,35
dc.b 36
MasterVolTab37:
dc.b 0,0,1,1,2,2,3,4,4,5,5,6,6,7,8,8
dc.b 9,9,10,10,11,12,12,13,13,14,15,15,16,16,17,17
dc.b 18,19,19,20,20,21,21,22,23,23,24,24,25,26,26,27
dc.b 27,28,28,29,30,30,31,31,32,32,33,34,34,35,35,36
dc.b 37
MasterVolTab38:
dc.b 0,0,1,1,2,2,3,4,4,5,5,6,7,7,8,8
dc.b 9,10,10,11,11,12,13,13,14,14,15,16,16,17,17,18
dc.b 19,19,20,20,21,21,22,23,23,24,24,25,26,26,27,27
dc.b 28,29,29,30,30,31,32,32,33,33,34,35,35,36,36,37
dc.b 38
MasterVolTab39:
dc.b 0,0,1,1,2,3,3,4,4,5,6,6,7,7,8,9
dc.b 9,10,10,11,12,12,13,14,14,15,15,16,17,17,18,18
dc.b 19,20,20,21,21,22,23,23,24,24,25,26,26,27,28,28
dc.b 29,29,30,31,31,32,32,33,34,34,35,35,36,37,37,38
dc.b 39
MasterVolTab40:
dc.b 0,0,1,1,2,3,3,4,5,5,6,6,7,8,8,9
dc.b 10,10,11,11,12,13,13,14,15,15,16,16,17,18,18,19
dc.b 20,20,21,21,22,23,23,24,25,25,26,26,27,28,28,29
dc.b 30,30,31,31,32,33,33,34,35,35,36,36,37,38,38,39
dc.b 40
MasterVolTab41:
dc.b 0,0,1,1,2,3,3,4,5,5,6,7,7,8,8,9
dc.b 10,10,11,12,12,13,14,14,15,16,16,17,17,18,19,19
dc.b 20,21,21,22,23,23,24,24,25,26,26,27,28,28,29,30
dc.b 30,31,32,32,33,33,34,35,35,36,37,37,38,39,39,40
dc.b 41
MasterVolTab42:
dc.b 0,0,1,1,2,3,3,4,5,5,6,7,7,8,9,9
dc.b 10,11,11,12,13,13,14,15,15,16,17,17,18,19,19,20
dc.b 21,21,22,22,23,24,24,25,26,26,27,28,28,29,30,30
dc.b 31,32,32,33,34,34,35,36,36,37,38,38,39,40,40,41
dc.b 42
MasterVolTab43:
dc.b 0,0,1,2,2,3,4,4,5,6,6,7,8,8,9,10
dc.b 10,11,12,12,13,14,14,15,16,16,17,18,18,19,20,20
dc.b 21,22,22,23,24,24,25,26,26,27,28,28,29,30,30,31
dc.b 32,32,33,34,34,35,36,36,37,38,38,39,40,40,41,42
dc.b 43
MasterVolTab44:
dc.b 0,0,1,2,2,3,4,4,5,6,6,7,8,8,9,10
dc.b 11,11,12,13,13,14,15,15,16,17,17,18,19,19,20,21
dc.b 22,22,23,24,24,25,26,26,27,28,28,29,30,30,31,32
dc.b 33,33,34,35,35,36,37,37,38,39,39,40,41,41,42,43
dc.b 44
MasterVolTab45:
dc.b 0,0,1,2,2,3,4,4,5,6,7,7,8,9,9,10
dc.b 11,11,12,13,14,14,15,16,16,17,18,18,19,20,21,21
dc.b 22,23,23,24,25,26,26,27,28,28,29,30,30,31,32,33
dc.b 33,34,35,35,36,37,37,38,39,40,40,41,42,42,43,44
dc.b 45
MasterVolTab46:
dc.b 0,0,1,2,2,3,4,5,5,6,7,7,8,9,10,10
dc.b 11,12,12,13,14,15,15,16,17,17,18,19,20,20,21,22
dc.b 23,23,24,25,25,26,27,28,28,29,30,30,31,32,33,33
dc.b 34,35,35,36,37,38,38,39,40,40,41,42,43,43,44,45
dc.b 46
MasterVolTab47:
dc.b 0,0,1,2,2,3,4,5,5,6,7,8,8,9,10,11
dc.b 11,12,13,13,14,15,16,16,17,18,19,19,20,21,22,22
dc.b 23,24,24,25,26,27,27,28,29,30,30,31,32,33,33,34
dc.b 35,35,36,37,38,38,39,40,41,41,42,43,44,44,45,46
dc.b 47
MasterVolTab48:
dc.b 0,0,1,2,3,3,4,5,6,6,7,8,9,9,10,11
dc.b 12,12,13,14,15,15,16,17,18,18,19,20,21,21,22,23
dc.b 24,24,25,26,27,27,28,29,30,30,31,32,33,33,34,35
dc.b 36,36,37,38,39,39,40,41,42,42,43,44,45,45,46,47
dc.b 48
MasterVolTab49:
dc.b 0,0,1,2,3,3,4,5,6,6,7,8,9,9,10,11
dc.b 12,13,13,14,15,16,16,17,18,19,19,20,21,22,22,23
dc.b 24,25,26,26,27,28,29,29,30,31,32,32,33,34,35,35
dc.b 36,37,38,39,39,40,41,42,42,43,44,45,45,46,47,48
dc.b 49
MasterVolTab50:
dc.b 0,0,1,2,3,3,4,5,6,7,7,8,9,10,10,11
dc.b 12,13,14,14,15,16,17,17,18,19,20,21,21,22,23,24
dc.b 25,25,26,27,28,28,29,30,31,32,32,33,34,35,35,36
dc.b 37,38,39,39,40,41,42,42,43,44,45,46,46,47,48,49
dc.b 50
MasterVolTab51:
dc.b 0,0,1,2,3,3,4,5,6,7,7,8,9,10,11,11
dc.b 12,13,14,15,15,16,17,18,19,19,20,21,22,23,23,24
dc.b 25,26,27,27,28,29,30,31,31,32,33,34,35,35,36,37
dc.b 38,39,39,40,41,42,43,43,44,45,46,47,47,48,49,50
dc.b 51
MasterVolTab52:
dc.b 0,0,1,2,3,4,4,5,6,7,8,8,9,10,11,12
dc.b 13,13,14,15,16,17,17,18,19,20,21,21,22,23,24,25
dc.b 26,26,27,28,29,30,30,31,32,33,34,34,35,36,37,38
dc.b 39,39,40,41,42,43,43,44,45,46,47,47,48,49,50,51
dc.b 52
MasterVolTab53:
dc.b 0,0,1,2,3,4,4,5,6,7,8,9,9,10,11,12
dc.b 13,14,14,15,16,17,18,19,19,20,21,22,23,24,24,25
dc.b 26,27,28,28,29,30,31,32,33,33,34,35,36,37,38,38
dc.b 39,40,41,42,43,43,44,45,46,47,48,48,49,50,51,52
dc.b 53
MasterVolTab54:
dc.b 0,0,1,2,3,4,5,5,6,7,8,9,10,10,11,12
dc.b 13,14,15,16,16,17,18,19,20,21,21,22,23,24,25,26
dc.b 27,27,28,29,30,31,32,32,33,34,35,36,37,37,38,39
dc.b 40,41,42,43,43,44,45,46,47,48,48,49,50,51,52,53
dc.b 54
MasterVolTab55:
dc.b 0,0,1,2,3,4,5,6,6,7,8,9,10,11,12,12
dc.b 13,14,15,16,17,18,18,19,20,21,22,23,24,24,25,26
dc.b 27,28,29,30,30,31,32,33,34,35,36,36,37,38,39,40
dc.b 41,42,42,43,44,45,46,47,48,48,49,50,51,52,53,54
dc.b 55
MasterVolTab56:
dc.b 0,0,1,2,3,4,5,6,7,7,8,9,10,11,12,13
dc.b 14,14,15,16,17,18,19,20,21,21,22,23,24,25,26,27
dc.b 28,28,29,30,31,32,33,34,35,35,36,37,38,39,40,41
dc.b 42,42,43,44,45,46,47,48,49,49,50,51,52,53,54,55
dc.b 56
MasterVolTab57:
dc.b 0,0,1,2,3,4,5,6,7,8,8,9,10,11,12,13
dc.b 14,15,16,16,17,18,19,20,21,22,23,24,24,25,26,27
dc.b 28,29,30,31,32,32,33,34,35,36,37,38,39,40,40,41
dc.b 42,43,44,45,46,47,48,48,49,50,51,52,53,54,55,56
dc.b 57
MasterVolTab58:
dc.b 0,0,1,2,3,4,5,6,7,8,9,9,10,11,12,13
dc.b 14,15,16,17,18,19,19,20,21,22,23,24,25,26,27,28
dc.b 29,29,30,31,32,33,34,35,36,37,38,38,39,40,41,42
dc.b 43,44,45,46,47,48,48,49,50,51,52,53,54,55,56,57
dc.b 58
MasterVolTab59:
dc.b 0,0,1,2,3,4,5,6,7,8,9,10,11,11,12,13
dc.b 14,15,16,17,18,19,20,21,22,23,23,24,25,26,27,28
dc.b 29,30,31,32,33,34,35,35,36,37,38,39,40,41,42,43
dc.b 44,45,46,47,47,48,49,50,51,52,53,54,55,56,57,58
dc.b 59
MasterVolTab60:
dc.b 0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14
dc.b 15,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29
dc.b 30,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44
dc.b 45,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59
dc.b 60
MasterVolTab61:
dc.b 0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14
dc.b 15,16,17,18,19,20,20,21,22,23,24,25,26,27,28,29
dc.b 30,31,32,33,34,35,36,37,38,39,40,40,41,42,43,44
dc.b 45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60
dc.b 61
MasterVolTab62:
dc.b 0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14
dc.b 15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30
dc.b 31,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45
dc.b 46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61
dc.b 62
MasterVolTab63:
dc.b 0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14
dc.b 15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30
dc.b 31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46
dc.b 47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62
dc.b 63
MasterVolTab64:
dc.b 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
dc.b 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31
dc.b 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47
dc.b 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63
dc.b 64
even
endc ; !MINIMAL
ifd SDATA
section __MERGED,bss
mt_chan1:
ds.b n_sizeof
mt_chan2:
ds.b n_sizeof
mt_chan3:
ds.b n_sizeof
mt_chan4:
ds.b n_sizeof
mt_SampleStarts:
ds.l 31
mt_mod:
ds.l 1
mt_oldLev6:
ds.l 1
mt_timerval:
ds.l 1
mt_oldtimers:
ds.b 4
mt_Lev6Int:
ds.l 1
mt_Lev6Ena:
ds.w 1
mt_PatternPos:
ds.w 1
mt_PBreakPos:
ds.w 1
mt_PosJumpFlag:
ds.b 1
mt_PBreakFlag:
ds.b 1
mt_Speed:
ds.b 1
mt_Counter:
ds.b 1
mt_SongPos:
ds.b 1
mt_PattDelTime:
ds.b 1
mt_PattDelTime2:
ds.b 1
ifeq MINIMAL
mt_SilCntValid:
ds.b 1
mt_MasterVolTab:
ds.l 1
endc
xdef _mt_Enable
_mt_Enable:
mt_Enable:
ds.b 1
xdef _mt_E8Trigger
_mt_E8Trigger:
mt_E8Trigger:
ds.b 1
ifeq MINIMAL
xdef _mt_MusicChannels
_mt_MusicChannels:
mt_MusicChannels:
ds.b 1
xdef _mt_SongEnd
_mt_SongEnd:
mt_SongEnd:
ds.b 1
endc ; !MINIMAL
else ; !SDATA : single section with local base register
rsreset
mt_chan1 rs.b n_sizeof
mt_chan2 rs.b n_sizeof
mt_chan3 rs.b n_sizeof
mt_chan4 rs.b n_sizeof
mt_SampleStarts rs.l 31
mt_mod rs.l 1
mt_oldLev6 rs.l 1
mt_timerval rs.l 1
mt_oldtimers rs.b 4
mt_Lev6Int rs.l 1
mt_Lev6Ena rs.w 1
mt_PatternPos rs.w 1
mt_PBreakPos rs.w 1
mt_PosJumpFlag rs.b 1
mt_PBreakFlag rs.b 1
mt_Speed rs.b 1
mt_Counter rs.b 1
mt_SongPos rs.b 1
mt_PattDelTime rs.b 1
mt_PattDelTime2 rs.b 1
ifeq MINIMAL
mt_SilCntValid rs.b 1
mt_MasterVolTab rs.l 1
endc
mt_Enable rs.b 1 ; exported as _mt_Enable
mt_E8Trigger rs.b 1 ; exported as _mt_E8Trigger
mt_MusicChannels rs.b 1 ; exported as _mt_MusicChannels
mt_SongEnd rs.b 1 ; exported as _mt_SongEnd
mt_data:
ds.b mt_Enable
xdef _mt_Enable
_mt_Enable:
ds.b 1
xdef _mt_E8Trigger
_mt_E8Trigger:
ds.b 1
ifeq MINIMAL
xdef _mt_MusicChannels
_mt_MusicChannels:
ds.b 1
xdef _mt_SongEnd
_mt_SongEnd:
ds.b 1
endc ; !MINIMAL
endc ; SDATA/!SDATA
endThe whole system as Verilog, without comments:
| file | what |
|---|---|
| verilog/top.v | the system: the 68000, the block RAM with the program and the
module, Paula, CIA-B, the mixer and the filter (module
top). This is what TARGET=verilator
builds. |
| verilog/tb.cpp | its C++ testbench: the frames into a .wav |
| verilog/board/top.v, verilog/board/st_arty.v | the bitstream's design: the system, the clock divider, the UART, the board's pins |
| verilog/board_dsp/top.v, verilog/board_dsp/st_dsp_arty.v | the DSP system's bitstream: the system, the five effects, the line
out, the Ethernet probe (bit/top_dsp.bit) |
| Paula, the custom registers, the 8520 CIA | Amiga Hardware Reference Manual: chapter 5, audio, chapter 7 (interrupts, DMA), appendix A (registers), appendix F (the 8520) -- the Amiga Developer CD v2.1 |
| Paula and the CIA in Verilog (transcribed) | MiSTer
Minimig-AGA: paula_audio_channel.v,
agnus_audiodma.v, paula_intcontroller.v,
cia_timerb.v, cia_int.v |
| Paula in an emulator | Clock Signal, Machines/Amiga/Audio.cpp; Amiga Machine Code Letter VIII -- Audio |
| The replay routine | ptplayer on Aminet (public domain); the original SoundTracker / NoiseTracker / ProTracker replays: RSE-UTS on Aminet |
| The module format | "Sound Tracker Module File Format", Steven W Green, 1990 (textfiles.com) |
| The module | "Neon Techno" by Drozerix, The Mod Archive (public domain) |
| The 68000 | Motorola M68000 Family Programmer's Reference Manual; the reference model Musashi |
| The assembler | vasm |
| The programmer | openFPGALoader (in the archive, with its source) |
| The effects' models | the C++ models of the compressor, chorus, flanger, delay and reverb
(identified from the reference devices' captures and impulse responses),
copied unchanged beside each l8 unit
(audio/dsp/filters/*/ref) |
| The DSP48E1 | Xilinx UG479, 7 Series DSP48E1 Slice User Guide |
| mu-law | ITU-T G.711; Sun Microsystems' g711.c
(linear2ulaw) |
| The windowed-sinc read, the polyphase resampler | J. O. Smith, Digital Audio Resampling (ccrma.stanford.edu/~jos/resample) |