Game Boy APU emulation. Provides an Apu object that can synthesize audio to an internal buffer when stepping for a given number of cycles. The emulated registers can be read/written to, which can be used along side a full Game Boy emulator, or to just generate audio when playing a module.
See also
Pan Docs for details about the hardware being emulated.
Types
Apu {.requiresInit.} = object ch1, ch2: PulseChannel ch3: WaveChannel ch4: NoiseChannel sweep: Sweep sequencer: Sequencer synth: Synth mix: array[4, MixMode] lastOutputs: array[4, uint8] time: uint32 leftVolume, rightVolume: int nr51: uint8 enabled: bool volumeStep: float32 autostep*: uint32 framerate: float cyclesPerFrame: float cycleOffset: float
- Game Boy APU emulator. Satisfies the ApuIo concept. Source Edit
Procs
proc availableSamples(a: Apu): int {....raises: [], tags: [].}
- Gets the amount of samples available in the buffer. takeSamples will take this exact amount of samples when called Source Edit
func channelFrequency(a: Apu; chno: ChannelId): int {....raises: [], tags: [].}
-
Gets the current frequency setting for the channel, for diagnostic purposes. Channels 1-3 will result in a value from 0 to 2047. Channel 4 will result in the contents of its NR43 register.
Example:
var a = Apu.init(44100) assert a.channelFrequency(ch2) == 0
Source Edit func channelMix(a: Apu; chno: ChannelId): MixMode {....raises: [], tags: [].}
-
Gets the current mix mode for the given channel. Provided as an alternative to reading ar51.
Example:
var a = Apu.init(44100) a.writeRegister(0x26, 0x80) # NR52 <- 0x80 a.writeRegister(0x25, 0b00110101) # NR51 <- 0x35 assert a.channelMix(ch1) == mixMiddle assert a.channelMix(ch2) == mixRight assert a.channelMix(ch3) == mixLeft assert a.channelMix(ch4) == mixMute
Source Edit func channelVolume(a: Apu; chno: ChannelId): int {....raises: [], tags: [].}
-
Returns a number from 0 to 15 resprenting the current volume level of the channel. For channels with an enevelope, this level is the current volume of the envelope. For the Wave channel, this value is the maximum possible determined by the wave volume setting (NR32).
Example:
var a = Apu.init(44100) assert a.channelVolume(ch1) == 0 assert a.channelVolume(ch3) == 0 a.writeRegister(0x26, 0x80) # NR52 <- 0x80 a.writeRegister(0x12, 0xD0) # NR12 <- 0xD0 a.writeRegister(0x14, 0x80) # NR14 <- 0x80 a.writeRegister(0x1C, 0x20) # NR32 <- 0x20 assert a.channelVolume(ch1) == 0xD assert a.channelVolume(ch3) == 0xF
Source Edit func init(_: typedesc[Apu]; samplerate: int; framerate = 59.7): Apu
-
Initializes an Apu with the given samplerate and internal buffer size that contains a single frame with the given framerate. samplerate and framerate are in Hz and must be greater than 0.
The returned Apu is in its default, or hardware reset, state. The volume step is set to a default of 0.625f.
Example:
var a = Apu.init(24000, 60.0) # 24000 Hz samplerate with a 60 Hz framerate
Source Edit func readRegister(a: var Apu; reg: uint8): uint8 {....raises: [], tags: [].}
-
Reads the register at address reg. This proc emulates the behavior of reading the memory-mapped registers on an actual Game Boy. Since some registers are write-only (ie frequency), attempting to read these registers will result in their bits being read back as all 1s.
reg is the memory address of the register in the 0xFF00 page, so to read rNR10, 0xFF10, you would call this proc with reg = 0x10u8
The read occurs at the apu's current timestep, run the apu beforehand if you want the read to occur at a certain point in time.
The proc will return 0xFF for any invalid reg address.
Example:
var a = Apu.init(44100) # sound is OFF, all reads should result in 0xFF assert a.readRegister(0x10) == 0xFFu8 # NR52 should be 0x70 assert a.readRegister(0x26) == 0x70u8
Source Edit proc removeSamples(a: var Apu) {....raises: [], tags: [].}
- Removes all samples in the sample buffer. Source Edit
proc reset(a: var Apu) {....raises: [], tags: [].}
-
Resets the apu to its initial state. Should behave similarly to a hardware reset. The internal sample buffer is also cleared.
Example:
var a = Apu.init(44100) a.writeRegister(0x26, 0x80) a.writeRegister(0x12, 0xF2) assert a.readRegister(0x12) == 0xF2u8 a.reset() assert a.readRegister(0x12) == 0xFFu8 assert a.availableSamples == 0
Source Edit proc run(a: var Apu; cycles: uint32) {....raises: [], tags: [].}
-
Runs the apu a for a given number of cycles. The internal sample buffer is updated with new samples from the run. Use takeSamples afterwards to collect them for processing, or removeSamples to discard them.
Example:
var a = Apu.init(44100, 1.0) a.run(4194304) # 1 second assert a.availableSamples == 44100 # another call to run will overrun the buffer # to empty the buffer do either: # a.takeSamples(buffer) # a.removeSamples()
Source Edit proc runToFrame(a: var Apu) {....raises: [], tags: [].}
-
Runs the apu a for the required number of cycles to complete a frame. The amount of cycles that is run is determined by a's current time and the framerate setting.
Example:
var a = Apu.init(48000, 60.0) a.runToFrame() # there are 800 samples in a frame # and 69905.06 cycles in a frame # so we step 69905 cycles which results in 799 samples instead # (eventually there will be one frame with 800 samples) assert a.availableSamples == 799
Source Edit proc setBufferSize(a: var Apu; samples: int) {....raises: [], tags: [].}
- Sets the apu's internal buffer to the given number of samples. This will destroy the contents of the buffer so it is recommended you call takeSamples first. Source Edit
proc setFramerate(a: var Apu; framerate: float) {....raises: [], tags: [].}
- Changes the size of frame to the given framerate. The APU must be reset when changing the framerate. Source Edit
proc setSamplerate(a: var Apu; samplerate: int) {....raises: [], tags: [].}
- Sets the samplerate of the generated audio. The internal sample buffer is left untouched, so it is recommended you call takeSamples beforehand. Source Edit
proc setVolume(a: var Apu; gain: range[0.0'f32 .. 1.0'f32]) {....raises: [], tags: [].}
-
Sets the volume level of a to the given linear gain value. The gain should range from 0.0f to 1.0f. The default volume level is a linear value of 0.625f or about -4 dB
Example:
var a = Apu.init(44100) a.setVolume(0.5f)
Source Edit proc takeSamples(a: var Apu; buf: var seq[Pcm]) {....raises: [], tags: [].}
- Takes out the entire sample buffer and puts it into the given buf. The buf's len will be set to a.availableSamples * 2 and will have the contents of the apu's sample buffer. Source Edit
func time(a: Apu): uint32 {....raises: [], tags: [].}
-
Gets the apu's current time, in cycles. Calling takeSamples resets the time to 0.
Example:
var a = Apu.init(44100) assert a.time == 0 a.run(1000) assert a.time == 1000 a.run(100) assert a.time == 1100 a.removeSamples() assert a.time == 0 # takeSamples will also reset time to 0
Source Edit proc writeRegister(a: var Apu; reg, value: uint8) {....raises: [], tags: [].}
-
Writes value to the apu's register at reg address. Like readRegister, this proc emulates writing to the Game Boy's memory-mapped APU registers. Writes to any unknown address are ignored. Writes to read-only portions of registers are also ignored. Like readRegister, the write occurs at the apu's current time.
Example:
var a = Apu.init(44100) a.writeRegister(0x12, 0xC0) # APU is off, write is ignored a.writeRegister(0x26, 0x80) # turn APU on assert a.readRegister(0x12) == 0x00 a.writeRegister(0x12, 0xC0) assert a.readRegister(0x12) == 0xC0 a.writeRegister(0xD3, 0xCC) # invalid register, write is ignored
Source Edit