Building Web-Based Audio Player for Obscure Audio Format

Outline

  • Definitions
  • Stories
  • Lessons

Motivation

🎶+📚->👨‍🎓

BRSTM

  • File format
  • Audio data, with loop point!
  • Used in Nintendo Wii
  • Popular in Nintendo modding community
How to play BRSTM?
BrawlBox

Why recreate it on web?

How to play audio on web?

<audio />

HTML's <audio /> cannot play BRSTM!

So how?

Let's start an audio player project

But first, we need a name

Neeku Vozo

Nikku

github.com/kenrick95/nikku

Idea (1)

ffmpeg

Big idea

  1. ffmpeg converts BRSTM to MP3
  2. Play using <audio src="music.mp3" />

Solved?

No, it's not able to loop

Idea (2)

Live stream

Big idea

  • Server serves the latest audio chunk continuously
  • Client listens like listening online radio

Live streaming protocols

  • HLS: HTTP Live Streaming (old Safari/Edge)
  • DASH: Dynamic Adaptive Streaming over HTTP (all other browsers)

The idea

  • Server implements DASH protocol
  • Client listens using DASH client library

Is the complexity getting out of hand?

What's the user flow like?

Project reset

Two big ideas

Static Server
SPA Client-server
User "uploads" BRSTM User uploads BRSTM
JS/WASM decode Server converts to MP3
Web Audio API plays HTML <audio /> to play or live streaming
Let's try static solution

Two main problems

  • How to decode BRSTM
  • How to play audio using Web Audio API

Extracting audio out of BRSTM file

<crash-course>

in Digital Audio

Analog to Digital

via Aquegg (CC BY-SA 3.0)

Pulse-code modulation (PCM)

  • Analog sound wave
  • Sampling
  • Quantization
  • Bit depth
  • Sample rate

Audio channels

via Fillbit (CC BY-SA 4.0)

4-minute track from a CD 💿

  • Sample rate: 44100 per second
  • Channels: 2
  • Bit depth: 16 bits per channel
  • Length: 240 seconds
  • 44100 /s × 2 × 16 bits × 240 s
    = 338,688,000‬ bits! ≈ 42.336 MB

Why your 4-minute audio file does not take up 42 MB of space?

Because compression!

Compression

  • Coding format: MP3, AAC, FLAC
  • Codec: encoder & decoder program
  • Container format: .mp3, .m4a, .flac

</crash-course>

Extracting BRSTM

For real

Only found two "wiki" pages describing the file!

Overview

  • Container Format: BRSTM
  • Coding Format: PCM or ADPCM
  • Other metdata specified in file

So I started a simple page that accepts a BRSTM file and read its metadata

It works. What's next?

Audio Player

Web Audio API

W3C - Web Audio API specs

Buffer Source node → Audio Context destination node


                const audioContext = new AudioContext();
                const bufferSource = audioContext.createBufferSource();
                bufferSource.connect(audioContext.destination);
            

Get PCM samples from BRSTM


              const pcm = brstm.getAllSamples(); // TODO: Implement
            

Transform PCM into audio buffer


              const audioBuffer = audioContext.createBuffer(
                1, // number of channels
                pcm.length,
                metadata.sampleRate
              );
              audioBuffer.getChannelData(0).set(pcm);
            

Write into source node, and plays it


              bufferSource.buffer = audioBuffer;
              bufferSource.start(0); // start immediately
            
What's left to do?

Decoding ADPCM

This is the coding format

How to turn ADPCM into PCM?

There should be a NPM package for this

imaadpcm

😱

and the input/output looks as expected!

So how's it?

🕵️‍

ADPCM

so many of them ...

MultimediaWiki

Decoding Nintendo ADPCM

That means I can't use that npm package

sigh
Scratching head

How did they do it?

ffmpeg
brstm.c
adpcm.c

How did they do it?

BrawlBox
How to find relevant codes?

Attach debugger!

ADPCMStream.cs
ADPCMState.cs

Eureka!

Is it finished?

No

Audio Player

Web Audio API Gotchas

Audio Player

  • Play/pause
  • Display current playback time
  • Seek
  • Loop

Looping

Easy


              // audioBuffer contains raw audio data (PCM)
              audioBuffer.loop = true
              audioBuffer.loopStart = … // in seconds
              audioBuffer.loopEnd = … // in seconds
            

Play/pause

Easy


              audioContext.suspend(); // pause
              audioContext.resume(); // resume
            

Seek


              // in <audio />
              audioElement.time = … // in seconds
            

Seek (in Web Audio)

  • Destroy audio buffer
  • Recreate the whole audio node connect thing
  • Start playing at certain time
    
                        bufferSource.start(0, soughtTimeInSeconds);
                      

Playback time display

Impossible


              audioContext.currentTime
            

Playback time display (workaround)

Reimplement timer

Finally

Marketing

Lessons

Define the project scope before starting anything
Do research, lots of research, especially if you are unfamiliar with the topic.
Don't be afraid to read others' codes. Codes never lie.
Web Audio API is kinda difficult to use 😢

Kenrick

@kenrick95
github.com/kenrick95/nikku
github.com/kenrick95/nikku-talk