Atari Lynx Mikey Audio Timers: Cascades, Waveform State, and PWM
Follow Lynx Mikey timer chains into audio channels, explaining reload and interrupt state, sample timing, PWM output, and emulator trace design.
Atari Lynx sound is generated by Mikey, a custom support chip that combines timers, audio channels, interrupt control, and other system functions. The important emulator lesson is that Lynx audio is timer-driven. A channel’s waveform and output rate emerge from timer state and timer interactions; they are not simply a list of samples handed to the host operating system.
The Lynx hardware reference preserves information from the original Handy hardware specification and audio notes. That documentation describes eight timers and four audio channels, with timer configuration, reload behavior, and audio-specific control. Treat those as related but separate layers. The timer creates events; an audio channel interprets those events as waveform transitions; the external audio path and host resampler turn them into output samples.
Mikey timers are programmable event sources
Each timer has control and counter state. Software configures a timer’s enable and mode, writes a count or backup value, and can select how its clock or another timer event advances the count. Some timer uses are independent time bases; others form cascaded relationships. A model that stores only the current counter loses the reload source, selected clock, prescaler phase, and pending interrupt state.
When a timer reaches its terminal condition, it may reload according to its configuration and generate an interrupt event. The order matters: software can observe the count, alter a reload value, acknowledge an interrupt, or disable the timer around a boundary. Represent the timer as an event machine with a deadline or phase accumulator, then connect its output to the interrupt controller through the documented flags and enables.
Timer chains should be explicit edges in the device graph. If one timer’s terminal event clocks another, the second timer should advance because of that emulated event, not because the host audio callback happened to run. This makes cascades reproducible at any host output rate and prevents a slower machine from changing gameplay timing.
Four audio channels and their timer relationship
The Lynx audio block exposes four channels. Each channel’s configuration relates a timer to audio frequency and waveform behavior; the audio notes describe controls for volume, output polarity, channel integration, and timer-based sample steps. Exact bit definitions should be taken from the original hardware specification rather than copied from a simplified console summary.
The model should retain raw register values and derived state separately. For example, changing a channel’s timer selection or backup value can alter future transitions without changing the sample already being emitted. A convenient host-side oscillator may cache a frequency, but it must be invalidated on every guest write that changes the underlying timer or mode. Prefer one emulated timer timeline and derive audio events from it.
The Lynx can use pulse-width modulation to represent a more complex output than a simple square wave. As the channel changes output level at timer events, the DAC path or analog filtering turns those transitions into a waveform. A host implementation should preserve the transition sequence and let the final resampler integrate it; converting every timer event directly to a coarse output frame can shift pulse widths and introduce aliasing.
This model also explains why “the pitch is approximately right” is a weak test. A channel can have the correct average frequency while starting one timer period late, using the wrong reload point, or losing an interrupt edge. Those errors may be audible as a click, duty-cycle change, or gradual phase shift even when a spectrum analyzer reports a similar fundamental.
Interrupts and sound state are coupled but distinct
Timer completion can be important to CPU code as well as sound generation. The CPU may rely on an interrupt to update a waveform, advance a software sequencer, or coordinate another peripheral. Keep the timer’s terminal status, interrupt enable, pending interrupt latch, and CPU-visible interrupt line as separate values. A masked event may still update device state even when it does not reach the CPU.
Audio writes can occur while a timer is active. The exact effect depends on which register changes and when the timer event next fires. Avoid applying every write immediately to a host voice if hardware latches some values at a boundary. Build a register-event trace and confirm the point of effect from the technical reference or a hardware-validated test.
If a game uses software mixing across multiple timer channels, channel ordering and interrupt priority can be audible. A race in the emulator’s host thread should never determine which guest channel updates first. Advance all device events in the Lynx’s deterministic scheduler and emit a trace sequence number whenever a timer output or interrupt changes state.
A compact cascade reference test
This Python example illustrates event propagation through a timer chain. It is a scheduler test, not a register-level Mikey implementation:
class Timer:
def __init__(self, count, reload_count):
self.count = count
self.reload_count = reload_count
self.underflows = 0
def clock(self):
if self.count > 0:
self.count -= 1
if self.count == 0:
self.underflows += 1
self.count = self.reload_count
return True
return False
def cascade(source, target, ticks):
events = 0
for _ in range(ticks):
if source.clock():
events += int(target.clock())
return events
The real chip’s count encoding, zero behavior, enable state, prescaling, and interrupt semantics must come from the hardware reference. The example only tests the architectural invariant that downstream time advances when an upstream timer emits its configured event.
Practical trace and regression strategy
For each timer, log guest writes, selected source clock, prescaler phase, counter transitions, reload value, terminal event, pending flag, and interrupt line. For each audio channel, log timer association, channel control, output transition, volume state, and the emulated timestamp. Correlate those logs to emitted host audio frames only after the guest event sequence is verified.
Test timer reload with counts at their minimum and maximum legal values, a timer disabled before expiry, reload changes just before terminal count, one timer feeding another, and interrupt disable/acknowledge sequences. Add audio tests for each channel independently, then test combinations where several timers expire at the same emulated cycle. Verify deterministic tie-breaking or documented event ordering.
Save states should preserve all eight timer counters, reload values, divider phases, chain connections, pending events, interrupt latches, and four channels’ waveform state. Include the current output level and next transition time. Restoring only an output sample buffer can make the first few milliseconds sound acceptable before the channel loses phase.
For hardware comparisons, use short recordings with stable input and power conditions, state the Lynx revision and capture path, and compare transitions as well as spectrum. If measurements are unavailable, label the test as emulator-to-emulator comparison; do not present software agreement as a verified physical measurement.
Acceptance criteria
A Mikey implementation should be able to explain every audio transition by naming the timer event, active channel registers, output state, and emulated time. It should also distinguish timer interrupt behavior from audio output behavior and preserve cascade phase across save and restore.
The key architectural idea is that the Lynx audio generator participates in the same device-event system as the rest of the machine. Treating it as four host oscillators can approximate a soundtrack, but it cannot guarantee timer-accurate software behavior. A shared, cycle-aware timer model makes both sound and interrupt behavior testable.
The documented timer and channel topology
The preserved Handy reference gives each timer a 3-bit clock selector, 8-bit down counter, 8-bit backup register, control bits, and status bits. Clock choices include 1, 2, 4, 8, 16, 32, or 64 microseconds, plus a link input driven by the preceding timer in a fixed chain. With reload enabled, a backup count of N produces N+1 source-clock intervals; zero is a valid full interval, not an empty timer. The source also documents a first-period range that can differ by one clock from steady-state reload periods, so initialization tests should distinguish the first timeout from subsequent ones.
The audio channels use the same time-multiplexed counter block as the system timers. Their prescaler, counter, and backup behavior are related to the timer implementation, but the channel output path adds a 12-bit polynomial shift register, selectable feedback taps, waveform/integrate behavior, signed volume, and an 8-bit DAC value. The four channel values are mixed and emitted through a PWM and filtering path. This explains why changing a timer can affect audio frequency without directly selecting a conventional oscillator waveform.
One channel’s timer event can clock the next device in the documented link chain, and the chain includes all four audio channels before returning to a timer. Model those links as explicit hardware wiring rather than arbitrary software callbacks. The reference also notes that the shared block is time-multiplexed and that register accesses can wait for the relevant slot. A cycle model that changes the timer and DAC instantly may therefore be a useful approximation, but it is not a validated bus-timing implementation. The sources below point directly to the timer/interrupt and audio chapters so a reader can check each register-level detail.
Related:
- Atari Lynx Suzy: Object Lists, Scaling, and Collision Buffers
- Atari 2600 TIA Audio: Divider Chains, AUDC Waveforms, and Clock-Accurate Sound
Sources: