# ============================================================================= # JAMDAC (FRAMEWORK FILE) VERSION 2026-07-30 # ============================================================================= # This file defines the MMIO types used with the io::audio_queues array, # which controls the Jamdac sound system. # # Jamdac's feature set is conceptually separated into its "core" (the smallest # complete synthesizer) plus a collection of "non-core" enhancements and # programming conveniences. # ----------------------------------------------------------------------------- # A sound parameter that can be controlled by an io_envelope class io_dynamic # 5 bytes # 0..2 = instrument envelopes # 3..4 = global envelopes [non-core] # # +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 # [non-core] # 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 # [non-core] 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 # [non-core] # 1.0 = simple string # 0.5 = drums # 0.0 = plucked bottle # Units: s6.10 fixed point var digitar_blend: pair # [non-core] # Delay loop length = 22,050 hz / digitar_hz - 0.5 # Units: Hertz inset digitar_hz: io_dynamic # [non-core] # 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 # [non-core] end class # ----------------------------------------------------------------------------- class io_channel_effect # size 30 [non-core] # 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 # [non-core] # 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 # [non-core] # 1=stereo left channel is delayed var apply_widener: byte end class # ----------------------------------------------------------------------------- class io_reverb # size 24 [non-core] # Units: buffer array size measured in samples inset stage_delay_sizes: pair[size 6] # Units: s6.10 fixed point inset stage_feedbacks: pair[size 6] 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 are non-core 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 purge_read_address <- write_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 read_address <- purge_read_address, discarding all events # 3. assign purge_read_address <- 0 var purge_read_address: int inset events: io_audio_event[inset 100] end class