Web & Wine 1/24
How to
flip
an audio
bit
Michael Leimstädtner
How to
flip
an audio
bit
Hello!
Michael
Software Engineer @
makandra
David
Techno Artist @
New Frames
Overview
Part I: Understanding the goal
Part II: Implementing a bit crushing effect
Part III: Actual bit flipping
(Part IV: Revisiting Oto Biscuit)
Part
I
Understanding our goal
## Prologue: Different kind of nerds
--- ## Prologue: Different kind of nerds
--- ## Could you help me with this code? > I need to disable or invert specific bits of an audio stream
--- ## Oto Biscuit
--- ## Audio terminology
Input
: Anything that makes a sound (microphone, audio file or synthetic sound)
Output
: Speakers or another audio processing device
Effect
: Anything that processes an audio stream
Audio channel
: Mono (one), Stereo (two), 5.1 etc.
Sample value
: A float representing a single value for the audio stream of a channel at a given time ([-1 .. 1])
Note: **Input**: A microphone, audio file, oscillator or synthesizer **Effect** ~= DSP = [Digital signal processors](https://en.wikipedia.org/wiki/Digital_signal_processor) Any filter or modularization ("the fun part") **Sample Size**: The number of samples per channel (quantization) **Frame**: All samples over all channels at a given time --- ## The plan  We want to "crush" each _sample_ from the float value space to only 2
N
distinct values, then modify those E.g.
1,065,353,215
-> 256
Part
II
Implementing a bit crushing effect
### Presenting ## Web Audio API  Note: * **AudioContext**: Pipes together **AudioNodes** and gives access to the **Web Audio API** * Reference to modular synthesizer: API just like that (Inputs <> Outputs) * Nodes must be connected. Multiple source s -> multiple effects -> one destination * **Web Audio API**: * Good documentation, implementation close to the modular hardware * **AudioWorklet Node** is part of the Main Thread * **AudioWorklet** is used to supply custom audio processing scripts that execute in a separate thread to provide very low latency audio processing. * This was a breakthrough in 2018, as it first allowed active modification of concurrent audio streams * **AudioWorklet Processor** is part of the WebAudio Render Thread * Inter-Process **communication** via parameters (once per sample rate) or async port messages * Legacy: single thread _ScriptProcessorNode_ * Code must be **fast**, we're having a real time app --- ## Base audio pipeline  ```html [1-5]
Connect Input
Pause
Resume
``` ```js const audioContext = new AudioContext() ``` --- ## Base audio pipeline: Audio Input ```js [1-2,18-19|3-10,15-17|11-14] function playDrumloop() { const drumLoopStream = audioContext.createBufferSource() const request = new XMLHttpRequest() request.open('GET', '/drumloop.wav', true) request.withCredentials = false request.responseType = 'arraybuffer' request.onload = () => { const audioData = request.response audioContext.decodeAudioData(audioData, function(buffer) { drumLoopStream.buffer = buffer drumLoopStream.playbackRate.value = 1.0 drumLoopStream.loop = true drumLoopStream.start(0) }) } request.send() drumLoopStream.connect(audioContext.destination) } ``` --- ## Base audio pipeline: Pausing the signal ```js [1-3|4-7] function pause() { audioContext.suspend() } function resume() { audioContext.resume() } ``` --- ## Base audio pipeline: Demo
--- ## Repeat: Audio Worklet Scopes  Note: * **AudioContext**: Pipes together **AudioNodes** and gives access to the **Web Audio API** * Reference to modular synthesizer: API just like that (Inputs <> Outputs) * Nodes must be connected. Multiple source s -> multiple effects -> one destination * **Web Audio API**: * Good documentation, implementation close to the modular hardware * **AudioWorklet Node** is part of the Main Thread * **AudioWorklet** is used to supply custom audio processing scripts that execute in a separate thread to provide very low latency audio processing. * This was a breakthrough in 2018, as it first allowed active modification of concurrent audio streams * **AudioWorklet Processor** is part of the WebAudio Render Thread * Inter-Process **communication** via parameters (once per sample rate) or async port messages * Legacy: single thread _ScriptProcessorNode_ * Code must be **fast**, we're having a real time app --- ## Bit crushing effect:
AudioWorkletNode ```html [1|2]
Connect Processor
``` ```js [2,13|3-7|8-11|1,12,15-17] let processorBitCount async function connectCustomProcessor() { await audioContext.audioWorklet.addModule( 'bit-crusher-processor.js', ) const bitCrusher = new AudioWorkletNode(audioContext, 'bit-crusher-processor') drumLoopStream.disconnect(audioContext.destination) drumLoopStream .connect(bitCrusher) .connect(audioContext.destination) processorBitCount = bitCrusher.parameters.get('bits') } function changeBitCount(slider) { processorBitCount.value = slider.value } ```
Part
III
Part III: Actual bit flipping
## How to actually flip a bit Example: Inverting the "fourth" bit of 55 ```plain 0000 0001 (1 in decimal) << 4 (left shift 4 times) ----------------- 0001 0000 ^ 0011 0111 (55 in decimal) ----------------- => 0010 0111 (final result) ``` ```js let sample = 55 // 32 + 16 + 4 + 2 + 1 const bitmask = 1 << 4 // 16 sample ^= bitmask sample === 39 // 55 - 16 ``` --- ## How to actually flip a bit ```js [1-8,20|9,10-12,18-19|13-17] // AudioWorkletProcessor: previous code is omitted process(inputs, outputs, parameters) { this.totalBits = parameters.bits[0] this.bitToFlip = parameters.flippedBit[0] outputs[channelIndex][sampleIndex] = this.flipBit(channelSample) } flipBit(sampleValue) { const possibleValuesCount = 2 ** this.totalBits // Round each sample value to its closest value (see previous example) const sign = Math.sign(sampleValue) let intSample = Math.abs(Math.floor(sampleValue * possibleValuesCount)) // Modify the int value by inverting/flipping the nth-bit const bitmask = 1 << (this.totalBits - this.bitToFlip) intSample ^= bitmask // Scale it back to [-1 .. 1] return (intSample * sign) / this.totalBits } ``` --- ## Bit flipping effect: Demo
Part
IV
Revisiting Oto Biscuit
## Oto Biscuit  --- ## Showcase: Bitflipper
Note: * Output device auf sof-hda-dsp * Input device auf Microphone USB Audio device * Handy mit Aux->USB anschließen + Spotify * Einzug der Gladiatoren * Redux auf 25% * Cutoff 2000 Hz * Q 10 * Links bits deaktivieren
The
end
--- ## Sources ```plain https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API https://developer.mozilla.org/en-US/docs/Web/API/AudioWorklet https://docs.google.com/presentation/d/11OZyHyWRTWOCETW4x7m5gCPNSdSl9HevCQ1aiR_0xFE/htmlpresent https://en.wikipedia.org/wiki/Digital_audio#/media/File:4-bit-linear-PCM.svg https://www.otomachines.com/wp-content/uploads/2015/07/biscuit_cam02_V02.jpg https://i.kym-cdn.com/entries/icons/original/000/001/007/WAT.jpg https://www.w3.org/TR/webaudio/images/audioworklet-instantiation-sequence.png https://www.producerspot.com/wp-content/uploads/2016/01/decimort2-sample-bit-rates.jpg https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/audio-context_.png https://s7d1.scene7.com/is/image/izotope/Reconstructing%20the%20original%20signal?fmt=webp-alpha&resMode=sharp2&wid=576& https://s7d1.scene7.com/is/image/izotope/Increasing%20bit-depth%20resolution?fmt=webp-alpha&resMode=sharp2&wid=590& https://upload.wikimedia.org/wikipedia/commons/f/f5/Byte45.png ```
talk.bitflipper.leimstaedtner.it
·
bitflipper.leimstaedtner.it
·
github.com/Ichaelus/bitflipper
Thank you for your time
Bonus
Slides
Listening to MIDI signals
Effect theory: Frequency Reduction & Filters
Implementing an oscilloscope
Tooling
Production readiness
## Bonus: Listening to MIDI signals * A MIDI signal consists of a
status
and two
data
values * It might be signal to turn on/off a note, control code or similar. ```js [1|2,4|3,5-8] const midiAccess = await navigator.requestMIDIAccess({ sysex: true }) for (const input of midiAccess.inputs) { input[1].onmidimessage = (e) => console.table(e.data, 'value'); } // (index) | Value // 0 | 176 // 1 | 4 // 2 | 22 ``` --- ## Bonus: MIDI debugging on linux ```sh [1-2|4-6|8-11] $ ls -ll /dev/snd/midi* # crw-rw----+ 1 root audio 116, 11 Jan 3 09:29 /dev/snd/midiC1D0 $ aseqdump -l # Port Client name Port name # 20:0 nanoKONTROL nanoKONTROL nanoKONTROL _ CTRL $ aseqdump -p 20:0 # Waiting for data. Press Ctrl+C to end. # Source Event Ch Data # 20:0 Control change 0, controller 2, value 125 ``` --- ## Effect theory: Frequency Reduction
--- ## Effect theory: Filter Filters allow gradual removing of different frequencies: * **lowpass** ignores high frequencies * **highpass** ignores low frequencies * **bandpass** ignores frequencies around the
cutoff
value The
Q
or
resonance
value adds a sinus wave at the cutoff value. --- ## Bonus: Implementing an oscilloscope ```js [1-4,21|6-9|10-11,20|12-19] function connectOscilloscope() { const oscilloscope = audioContext.createAnalyser() drumLoopStream.connect(oscilloscope) const freqData = new Uint8Array(oscilloscope.frequencyBinCount) const exampleOutput = document.querySelector('#firstFrequency') const minOutput = document.querySelector('#minFrequency') const maxOutput = document.querySelector('#maxFrequency') const averageOutput = document.querySelector('#averageFrequency') setInterval(() => { oscilloscope.getByteTimeDomainData(freqData) const averageFrequency = freqData.reduce((f1, f2) => { return f1 + f2 }, 0) / freqData.length exampleOutput.innerText = freqData[0] minOutput.innerText = Math.min(...freqData) maxOutput.innerText = Math.max(...freqData) averageOutput.innerText = averageFrequency }, 100) } ``` --- ## Bonus slide: Tooling * Chrome devtools:
WebAudio
tab (sample rate, audio context state,) * Firefox Devtools: Better but deprecated. Maybe part of Firefox developer edition. --- ## Bonus: Production readiness * You need SSL, User permission, a click interaction and probably some input/output mangling * Good Browser support since 2021