[jamdac]
# =============================================================================
# JAMDAC (FRAMEWORK FILE) VERSION 2025-10-26
# =============================================================================
# This file defines the MMIO types used with the io::audio_queues array,
# which controls the jamdac sound system.
# -----------------------------------------------------------------------------
# A sound parameter that can be controlled by an io_envelope
class io_dynamic # 5 bytes
# 0..2 = instrument envelopes
# 3..4 = global envelopes
#
# +32 = enable envelope, if disabled then "high" is used
# +64 = enable pitch_factor control
# +128 = enable mod_factor control
var control: byte
# The value when l=0 in the envelope graph
var low: pair
# The value when l=255 in the envelope graph
var high: pair
end class
# -----------------------------------------------------------------------------
class io_envelope # 20 bytes
var samples_per_t: pair
# 1 = sustain; without this flag, sustain_index is ignored
# 2 = looping
# 4 = stairsteps instead of interpolation
var flags: byte
# Index of the graph point to be held until a "RELEASE INSTRUMENT" command
var sustain_index: byte # [Jamdac Plus]
# Eight (l, t) pairs
# l: 0..255 --> low..high of io_dynamic level
# t: 0..255 --> 0..255*samples_per_t time step size
# If a point has t=0, it creates an open interval with a discontinuity.
# If two consecutive points have t=0, then the second point and remainder
# are discarded. If the eighth point has t>0, it interpolates to l=graph[0].
inset graph: byte[size 16]
end class
# -----------------------------------------------------------------------------
class io_wave # size 30
# 0=none, 1=triangle, 2=saw, 3=pulse, 4=noise
var oscillator_shape: byte
# Units: Hertz
inset oscillator_hz: io_dynamic
# Units: s6.10 fixed point
inset oscillator_level: io_dynamic
# Units: s6.10 fixed point, range 0.0 .. 1.0
# 0.0 produces constant output of -1.0
# 1.0 produces constant output of 1.0
inset pulse_width: io_dynamic
# 0 = none
# 1 = lowpass
# 2 = bandpass
# 3 = highpass
# 4 = notch
# 5 = peak
var filter_kind: byte
# Units: Hertz indicating the filter's cutoff frequency
inset filter_hz: io_dynamic
# Units: s6.10 fixed point, range 0.0 .. 1.0
var filter_resonance: pair
# Units: s6.10 fixed point
inset output_level: io_dynamic
# 0=dry, 1=channel effect
var apply_effect: byte
end class
# -----------------------------------------------------------------------------
class io_wave_with_digitar extends io_wave # size 43
# +1 = enable digitar
# +2 = enable digitar lowpass smoothing
# +4 = enable input gate
# Note: wave_b's digitar uses the same buffer as the channel effect, so
# enabling wave_b's digitar will disable the channel effect.
var digitar_mode: byte # [Jamdac Plus]
# 1.0 = simple string
# 0.5 = drums
# 0.0 = plucked bottle
# Units: s6.10 fixed point
var digitar_blend: pair # [Jamdac Plus]
# Delay loop length = 22,050 hz / digitar_hz - 0.5
# Units: Hertz
inset digitar_hz: io_dynamic # [Jamdac Plus]
# Normally this is always 1.0, but the digitar can be used
# for conventional delay loop effects.
# Units: s6.10 fixed point
inset digitar_feedback: io_dynamic # [Jamdac Plus]
end class
# -----------------------------------------------------------------------------
class io_channel_effect # size 30
# Units: s6.10 fixed point
inset distortion_gain: io_dynamic
# Amount of delay, measured in samples. Value must be between 1 and 2205,
# otherwise the output is zero.
inset delay_samples: io_dynamic
# Units: s6.10 fixed point
inset delay_feedback: io_dynamic
# Units: s6.10 fixed point
inset dry_level: io_dynamic
# Units: s6.10 fixed point
inset predelay_level: io_dynamic
# Units: s6.10 fixed point
inset postdelay_level: io_dynamic
end class
# -----------------------------------------------------------------------------
class io_instrument # size 222
inset envelopes: io_envelope[inset 3]
inset wave_a: io_wave_with_digitar
inset wave_b: io_wave_with_digitar
inset wave_c: io_wave
inset channel_effect: io_channel_effect
# Units: s6.10 fixed point
inset left_level: io_dynamic
# Units: s6.10 fixed point
inset right_level: io_dynamic
# Units: s6.10 fixed point
inset reverb_level: io_dynamic # [Jamdac Plus]
# 1=stereo left channel is delayed
var apply_widener: byte
end class
# -----------------------------------------------------------------------------
class io_reverb # size 24
# Units: buffer array size measured in samples
inset stage_delay_sizes: pair[size 6] # [Jamdac Plus]
# Units: s6.10 fixed point
inset stage_feedbacks: pair[size 6] # [Jamdac Plus]
end class
# -----------------------------------------------------------------------------
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