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.