libtrackerboy/apu

Source   Edit  

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
  autostep*: uint32
  
Game Boy APU emulator. Satisfies the ApuIo concept. Source   Edit  

Procs

proc availableSamples(a: Apu): int {....raises: [], tags: [], forbids: [].}
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: [],
    forbids: [].}
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: [],
    forbids: [].}
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: [],
    forbids: [].}
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(__1728053970: typedesc[Apu]; samplerate: int; framerate = 59.7): Apu {.
    ...raises: [].}

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: [],
    forbids: [].}

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: [], forbids: [].}
Removes all samples in the sample buffer. Source   Edit  
proc reset(a: var Apu) {....raises: [], tags: [], forbids: [].}
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: [], forbids: [].}
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: [], forbids: [].}
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: [], forbids: [].}
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: [],
    forbids: [].}
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: [],
    forbids: [].}
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: [], forbids: [].}
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: [],
    forbids: [].}
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: [], forbids: [].}
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: [],
    forbids: [].}
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