december_adventure_2023

December Adventure (2023)
git clone https://git.eamoncaddigan.net/december_adventure_2023.git
Log | Files | Refs | README

audio_notes.md (3767B)


      1 # How does Audio work in uxn's reference implementation?
      2 
      3 This is just a collection of notes, I'm not trying to form a coherent
      4 narrative here!
      5 
      6 ## The code
      7 
      8 Most of the implementation lives in
      9 [audio.c](https://git.sr.ht/~rabbits/uxn/tree/main/item/src/devices/audio.c).
     10 The notable exception is `audio_deo` in
     11 [uxnemu.c](https://git.sr.ht/~rabbits/uxn/tree/main/item/src/uxnemu.c). This
     12 last function is where uxn calls SDL's audio handling, which triggers when a
     13 value is written to the `pitch` port (see below). `audio_finished_handler`
     14 is also defined in `uxnemu.c`. This provides nice separation between generic
     15 audio stuff and SDL functions, although `audio_handler` is an
     16 [SDL_AudioSpec](https://wiki.libsdl.org/SDL2/SDL_AudioSpec) callback.
     17 
     18 Many (maybe "most", but I'm not counting) of the functions are `void` type
     19 and modify their arguments.
     20 
     21 The header file
     22 [audio.h](https://git.sr.ht/~rabbits/uxn/tree/main/item/src/devices/audio.h)
     23 has a signature for an `audio_render` that I can't find defined anywhere.
     24 
     25 Only `audio_dei`, `audio_handler`, and `audio_start` are defined in
     26 `audio.c` and referenced elsewhere in the code (all in `uxnemu.c`).
     27 
     28 ## The ports
     29 
     30     |30 @Audio0 [ &vector $2 &position $2 &output $1 &duration $2 &pad $1 &adsr $2 &length $2 &addr $2 &volume $1 &pitch $1 ]
     31 
     32 `duration` was added in the October refactor, and `asdr` works differently
     33 than it used to.
     34 
     35 ## Pitch
     36 
     37 The low seven bits of the pitch byte corresponds to a [midi
     38 note](https://wiki.xxiivv.com/site/midi.html) (microtonality is supposedly
     39 possible if I'm reading the Git logs correctly, but I'm not sure how that's
     40 implemented yet).
     41 
     42 Sending a pitch of `0x00` appears to stop playback.
     43 
     44 The lowest supported note is `0x20`, 
     45 
     46 The highest bit of the pitch byte (still) controls looping behavior (so the
     47 highest pitch supported is `0x7f`, an 8-octave range.
     48 
     49 Notes are converted to frequencies by the `tuning` lookup table. This table
     50 is 109 elements long even though there are only 96 possible notes?
     51 
     52 The elements in the tuning table represent each note's frequency (in Hz)
     53 divided by 44104.31 (95% CI: 44104.30-44104.32), approximately the value of
     54 the `SAMPLE_FREQUENCY` (44100.0 Hz) defined in
     55 [audio.h](https://git.sr.ht/~rabbits/uxn/tree/main/item/src/devices/audio.h).
     56 I.e., this value gives the (fractional) number of cycles of the waveform per
     57 audio sample for a given note.
     58 
     59 This "fractional number of cycles" eventually needs to be converted into an
     60 integer number of steps by taking the sample length into account.
     61 
     62 ## Playing a note
     63 
     64 The `note_on` function appears to get things ready for a note to play, but
     65 playing itself happens in the `audio_handler`. This function is called with
     66 the "userdata" field, which is a pointer to the current `Uxn` structure
     67 (which contains the stacks, program memory, and devices), a pointer to a
     68 region of memory comprising the "audio data buffer", and an integer
     69 specifying the length thereof.
     70 
     71 SDL is probably responsible for allocating and freeing those buffers, which
     72 means that uxn just needs to fill that thing with audio sample data. How
     73 does it do that? First (and this bit surprises me), uxn is handling the
     74 polyphony; 
     75 
     76 `SOUND_TIMER` has a value of (256 / 44100 * 1000) ~5.80, which is the
     77 duration in ms of a 256 sample buffer. `duration` is also expressed in ms,
     78 but this 5.8 ms `SOUND_TIMER` limits the resolution of playback.
     79 
     80 ### The ADSR
     81 
     82 The S value of the ADSR envelope now specifies a fraction of the peak A/D
     83 volume that is maintained during the sustain phase.
     84 
     85 A, D, and R are multiplied by 64 (in `note_on`), and then (in `env_on`) they
     86 are inverted and multiplied by `SOUND_TIMER` (5.8 ms) / `AUDIO_BUFSIZE`
     87 (256 samples).
     88 
     89 The attack stage can be skipped (with an initial A of 0) but decay can't.