Skip to content

Instantly share code, notes, and snippets.

@automata
Last active February 3, 2022 00:15
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save automata/5832104 to your computer and use it in GitHub Desktop.
Save automata/5832104 to your computer and use it in GitHub Desktop.
emscripten + audio data api
to get it working, install Emscripten:
https://github.com/kripken/emscripten/wiki/Tutorial
(on Ubuntu: https://github.com/kripken/emscripten/wiki/Getting-Started-on-Ubuntu-12.10)
set all the env variables and run build.sh.
open sin_audiodata.html in your Firefox.
#!/bin/bash
emcc sin.cpp -o sin.js \
-s EXPORTED_FUNCTIONS="['_Sin_constructor','_Sin_destructor','_Sin_setFrequency','_Sin_setAmplitude','_Sin_getFrequency','_Sin_computeBuffer']"
cat sin-proxy.js >> sin.js
// get references to the exposed proxy functions
var Sin_constructor = Module.cwrap('Sin_constructor', 'number', ['number', 'number']);
var Sin_destructor = Module.cwrap('Sin_destructor', null, ['number']);
var Sin_setFrequency = Module.cwrap('Sin_setFrequency', null, ['number', 'number']);
var Sin_setAmplitude = Module.cwrap('Sin_setAmplitude', null, ['number', 'number']);
var Sin_getFrequency = Module.cwrap('Sin_getFrequency', 'number', ['number']);
var Sin_computeBuffer = Module.cwrap('Sin_computeBuffer', null, ['number', 'number', 'number', 'number']);
Sin = function (freq, amp) {
that = {};
that.ptr = Sin_constructor(freq, amp);
that.destroy = function () {
Sin_destructor(that.ptr);
};
that.setFrequency = function (x) {
Sin_setFrequency(that.ptr, x);
};
that.setAmplitude = function (x) {
Sin_setAmplitude(that.ptr, x);
};
that.getFrequency = function () {
return Sin_getFrequency(that.ptr);
};
that.computeBuffer = function (buffer, size, samplePos) {
Sin_computeBuffer(that.ptr, buffer, size, samplePos);
};
return that;
};
#include <new>
#include <cmath>
#include <cstdlib>
static const float PI = 3.14159265358979f;
using namespace std;
class Sin {
public:
float amplitude, frequency, sampleRate;
Sin (float frequencyV, float amplitudeV) {
this->frequency = frequencyV;
this->amplitude = amplitudeV;
this->sampleRate = 44100;
}
~Sin() {
}
void setFrequency(float v) {
this->frequency = v;
}
void setAmplitude(float v) {
this->amplitude = v;
}
float getFrequency() {
return this->frequency;
}
void computeBuffer(float *buffer, int size, int samplePos) {
int i;
float k = 2 * PI * this->frequency / this->sampleRate;
for (i=0; i<size; i++) {
buffer[i] = sin(k * samplePos++);
}
}
};
//compile using "C" linkage to avoid name obfuscation
extern "C" {
//constructor, returns a pointer to the HelloWorld object
void *Sin_constructor(float freq, float amp) {
Sin* obj = new Sin(freq, amp);
return obj;
}
void Sin_setFrequency(Sin *s, float freq) {
s->setFrequency(freq);
}
void Sin_setAmplitude(Sin *s, float amp) {
s->setAmplitude(amp);
}
float Sin_getFrequency(Sin *s) {
return s->getFrequency();
}
void Sin_computeBuffer(Sin *s, float *buffer, int size, int samplePos) {
s->computeBuffer(buffer, size, samplePos);
}
void Sin_destructor(Sin *s) {
delete s;
}
};
// main() {
// float *buf;
// Sin s (220.0, 1.5);
// s.setFrequency(440.0);
// s.computeBuffer();
// buf = s.buffer;
// cout << buf[1] << endl;
// return 0;
// }
<!DOCTYPE html>
<html>
<head>
<title> sin </title>
<script src="sin.js"></script>
</head>
<body>
<input type="text" size="4" id="freq" value="220"><label for="hz">Hz</label>
<button onclick="start()">play</button>
<button onclick="stop()">stop</button>
<script type="text/javascript">
function AudioDataDestination(sampleRate, readFn) {
var audio = new Audio();
audio.mozSetup(1, sampleRate);
var currentWritePosition = 0;
var prebufferSize = sampleRate / 2; // buffer 500ms
var tail = null, tailPosition;
setInterval(function() {
var written;
// Check if some data was not written in previous attempts.
if(tail) {
written = audio.mozWriteAudio(tail.subarray(tailPosition));
currentWritePosition += written;
tailPosition += written;
if(tailPosition < tail.length) {
// Not all the data was written, saving the tail...
return; // ... and exit the function.
}
tail = null;
}
// Check if we need add some data to the audio output.
var currentPosition = audio.mozCurrentSampleOffset();
var available = currentPosition + prebufferSize - currentWritePosition;
if(available > 0) {
// Request some sound data from the callback function.
var soundData = new Float32Array(available);
readFn(soundData);
// Writting the data.
written = audio.mozWriteAudio(soundData);
if(written < soundData.length) {
// Not all the data was written, saving the tail.
tail = soundData;
tailPosition = written;
}
currentWritePosition += written;
}
}, 100);
}
// Control and generate the sound.
var frequency = 0, currentSoundSample;
var sampleRate = 44100;
function requestSoundData(soundData) {
if (!frequency) {
return; // no sound selected
}
// get the length of the data in bytes
var bufferBytes = soundData.length * soundData.BYTES_PER_ELEMENT;
// malloc enough space for the data
var bufferPtr = _malloc(bufferBytes);
// get a bytes-wise view on the newly allocated buffer
var heapBytes= new Uint8Array(Module.HEAPU8.buffer, bufferPtr, bufferBytes);
// copy data into heapBytes
heapBytes.set(new Uint8Array(soundData.buffer));
// call the c function which should modify the vals
s = Sin(frequency, 1.0);
s.computeBuffer(heapBytes.byteOffset, soundData.length, currentSoundSample);
// print out the results of the c function
var heapFloats= new Float32Array(heapBytes.buffer, heapBytes.byteOffset,
soundData.length);
for (var i=0, size=soundData.length; i<size; i++) {
soundData[i] = heapFloats[i];
}
currentSoundSample += soundData.length;
_free(heapBytes.byteOffset);
}
var audioDestination = new AudioDataDestination(sampleRate, requestSoundData);
function start() {
currentSoundSample = 0;
frequency = parseFloat(document.getElementById("freq").value);
}
function stop() {
frequency = 0;
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment