Jamdac audio
The Jamdac audio synthesizer enables Hybrix programs to play music and sound effects. Here we'll summarize its IO interface; for complete details, consult the Jamdac Reference book.
Specifications
- 22,050 samples per second stereo with 16-bit samples
- 6 synthesizer channels, each able to play one instrument at a time
- Each instrument/channel can play 3 simultaneous oscillators and 1 filter
- 20 programmable envelopes for controlling dynamics
- Global reverb
The Jamdac hardware is a separate system, comprised of a DSP unit, event coprocessor, and small dedicated audio memory. The coprocessor is actually a second Chombit CPU, but running a firmware ROM that (currently) cannot be modified.
Audio queues
In order to avoid glitches or lag, audio synthesis requires very precise realtime timings. The main program cannot provide such guarantees. Instead, it inserts commands into the IO_AUDIO_QUEUE::EVENTS, and the Jamdac coprocessor will remove (dequeue) the events as they are played. The events represent primitive commands such as loading an instrument or triggering a note. This scheme is conceptually similar to the MIDI standard, except that each Jamdac "channel" corresponds to a physical synthesizer voice, and each of the six channels has its own IO_AUDIO_QUEUE::EVENTS array.
The queue is a circular buffer whose start and end are tracked by IO_AUDIO_QUEUE.READ_ADDRESS and IO_AUDIO_QUEUE.WRITE_ADDRESS.
Timing
Audio synchronization is controlled by two special timing events:
-
WAIT(IO_AUDIO_EVENT.KIND= 8): Tells the synthesizer to wait a certain number of cycles before proceeding with the next command on that channel. -
SYNC(IO_AUDIO_EVENT.KIND= 9): Synchronizes a subset of channels; they each stop when they get to theSYNCevent, and then when the last channel has stopped, they all start together.
Framework tracker
The Hybrix designer implements a complete music tracker and instrument editor, and the [SOUND] framework file plays the songs by generating IO_AUDIO_EVENT events. Because the designer emits the native Jamdac IO_INSTRUMENT data structures, the player requires relatively little code. The Hybrix designer can also export Jamdac chiptune files.
I/O definitions
CLASS IO_AUDIO_EVENT # SIZE 5
# 0 = LOAD INSTRUMENT OPERAND = ADDRESS OF IO_INSTRUMENT
# OR NULL TO RESET
# 1 = TRIGGER INSTRUMENT OPERAND = MOD_FACTOR IN HIGH PAIR;
# PITCH_FACTOR IN LOW PAIR
# (VALUES ARE S6.10 FIXED POINT)
# * 2 = RELEASE INSTRUMENT
# * 3 = LOAD GLOBAL ENVELOPE #3 OPERAND = ADDRESS OF IO_ENVELOPE
# OR NULL TO RESET
# * 4 = LOAD GLOBAL ENVELOPE #4 OPERAND = ADDRESS OF IO_ENVELOPE
# OR NULL TO RESET
# * 5 = TRIGGER GLOBAL ENVELOPE OPERAND = #3 OR #4
# * 6 = RELEASE GLOBAL ENVELOPE OPERAND = #3 OR #4
# * 7 = LOAD GLOBAL REVERB OPERAND = ADDRESS OF IO_REVERB
# OR NULL TO RESET
#
# 8 = WAIT OPERAND = NUMBER OF ADDITIONAL SAMPLES
# 9 = SYNC OPERAND = SYNC GROUP (0..5) IN BYTE 3;
# CHANNEL COUNT (1..6) IN BYTE 4
# * 10 = RAMPDOWN 30 SAMPLES
#
# * THESE FEATURES REQUIRE JAMDAC PLUS
VAR KIND: BYTE
VAR OPERAND: INT
END CLASS
CLASS IO_AUDIO_QUEUE # SIZE 512
# ADDRESS OF WHERE THE NEXT EVENT WILL BE WRITTEN BY THE APP
VAR WRITE_ADDRESS: INT
# ADDRESS OF THE NEXT EVENT TO BE READ BY THE AUDIO SYSTEM
# QUEUE IS EMPTY WHEN: WRITE_ADDRESS = READ_ADDRESS
# QUEUE IS FULL WHEN: WRAP(WRITE_ADDRESS + SIZEOF(EVENT)) = READ_ADDRESS
VAR READ_ADDRESS: INT
# TO PURGE THE QUEUE, ASSIGN WRITE_ADDRESS -> PURGE_READ_ADDRESS.
# WHEN THE JAMDAC SEES THAT PURGE_READ_ADDRESS IS NONZERO, IT WILL:
# 1. IF A "WAIT" OR "SYNC" EVENT IS ACTIVE, IT WILL BE ENDED EARLY
# 2. ASSIGN PURGE_READ_ADDRESS -> READ_ADDRESS, DISCARDING ALL EVENTS
# 3. ASSIGN 0 -> PURGE_READ_ADDRESS
VAR PURGE_READ_ADDRESS: INT
INSET EVENTS: IO_AUDIO_EVENT[INSET 100]
END CLASS
MODULE IO
. . .
# 0 = DISABLE JAMDAC; 1 = ENABLE JAMDAC, REBOOTING ITS CPU
VAR JAMDAC_ENABLE: BYTE LOCATED AT $D0_003E
. . .
INSET AUDIO_QUEUES: IO_AUDIO_QUEUE[INSET 6] LOCATED AT $D0_4000 # ..$D0_4BFF
END MODULE