Skip to content
This repository has been archived by the owner on Nov 3, 2021. It is now read-only.

Commit

Permalink
Created c-api and stackmat.js to wrap it using webworkers. stackmat.h…
Browse files Browse the repository at this point in the history
…tml is a basic demo.
  • Loading branch information
jfly committed Jun 3, 2014
1 parent deba1db commit 08f9bd8
Show file tree
Hide file tree
Showing 8 changed files with 1,782 additions and 1,192 deletions.
6 changes: 1 addition & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ py: $(BLD)/_fskube.so

js: $(BLD)/fskube.js

jsmin: $(BLD)/fskube.min.js

# Autogenerated dependencies trick from
# http://scottmcpeak.com/autodepend/autodepend.html
-include $(PYTHONWRAPPER_OBJS:.o=.d)
Expand Down Expand Up @@ -58,12 +56,10 @@ $(BLD)/fskube.py $(BLD)/fskube_wrap.cpp $(BLD)/fskube_wrap.h: $(FSKUBE_SRCS) $(S
swig -builtin -python -c++ -o $(BLD)/fskube_wrap.cpp $(SRC)/fskube.i

# Similar dependency trick as above.
# TODO "--closure 1 -O3" to compress resulting javascript
$(BLD)/fskube.js: $(FSKUBE_SRCS) $(SRC)/embind.cpp $(SRC)/wrap_c.js
em++ $(CFLAGS) $(INC) --bind $(SRC)/embind.cpp $(FSKUBE_SRCS) -s EXPORTED_FUNCTIONS="['_getLogLevels', '_setLogLevels']" --post-js $(SRC)/wrap_c.js -o $@

$(BLD)/fskube.min.js: $(FSKUBE_SRCS) $(SRC)/embind.cpp $(SRC)/wrap_c.js
em++ $(CFLAGS) $(INC) --bind $(SRC)/embind.cpp $(FSKUBE_SRCS) -s EXPORTED_FUNCTIONS="['_getLogLevels', '_setLogLevels']" --post-js $(SRC)/wrap_c.js -o $@ --closure 1 -O3

check: py
PYTHONPATH=$(BLD) python3 -m unittest discover -s test/ -p *Test.py

Expand Down
2,726 changes: 1,573 additions & 1,153 deletions release/fskube.js

Large diffs are not rendered by default.

117 changes: 117 additions & 0 deletions release/stackmat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
var Stackmat = Stackmat || {};

// webkit shim
window.AudioContext = window.AudioContext || window.webkitAudioContext;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

(function() {
"use strict";

var fskubeWorkerUrl;
var worker;

var audioContext;
// Leak these nodes so the browser won't clean them up. Gross.
var scriptProcessor;
var microphoneInput;

function createMicrophoneScriptProcessor() {
navigator.getUserMedia(
{ audio: true },
function(stream) {
microphoneInput = audioContext.createMediaStreamSource(stream);
microphoneInput.connect(scriptProcessor);
microphoneScriptProcessorCreated();
},
function(e) {
alert('No live audio input: ' + e);
}
);

audioContext = new AudioContext();
// do the shimmy
audioContext.createScriptProcessor = ( audioContext.createScriptProcessor ||
audioContext.createJavaScriptNode );

// A bigger buffer means more latency, but also gives us a better chance of not dropping
// samples.
var bufferSize = 8*1024;
scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);

// This is silly. Apparently ScriptProcessorNode's need to be connected
// to an output in order to fire.
// http://stackoverflow.com/questions/19482155/do-webaudio-scriptprocessornodes-require-an-output-to-be-connected
var zeroGainNode = audioContext.createGain();
zeroGainNode.gain = 0;
scriptProcessor.connect(zeroGainNode);
zeroGainNode.connect(audioContext.destination);
}

function microphoneScriptProcessorCreated() {
worker = new Worker(fskubeWorkerUrl);

var methods = {
getLogLevels: function(logLevels) {
if(getLogLevelsCallback) {
getLogLevelsCallback(logLevels);
getLogLevelsCallback = null;
}
},
newState: function(state) {
if(Stackmat.onstackmatstate) {
Stackmat.onstackmatstate(state);
}
}
};
worker.addEventListener("message", function(e) {
methods[e.data.method].apply(null, e.data.args);
});
worker.addEventListener("error", function(e) {
console.error("Worker error: " + e.message + "\n");
throw error;
});

worker.postMessage({
method: "initialize",
args: [ audioContext.sampleRate ]
});

scriptProcessor.onaudioprocess = function(e) {
worker.postMessage({
method: "addSample",
args: [ e.inputBuffer.getChannelData(0) ]
});
};
}

Stackmat.initialize = function(fskubeWorkerUrl_) {
fskubeWorkerUrl = fskubeWorkerUrl_;
createMicrophoneScriptProcessor();
};

Stackmat.onstackmatstate = null;

Stackmat.setLogLevels = function(levels) {
if(!worker) {
console.error("Must call Stackmat.initialize() first");
return;
}
worker.postMessage({
method: "setLogLevels",
args: [ levels ]
});
};

var getLogLevelsCallback;
Stackmat.getLogLevels = function(cb) {
if(!worker) {
console.error("Must call Stackmat.initialize() first");
return;
}
getLogLevelsCallback = cb || function(levels) { console.log(levels); };
worker.postMessage({
method: "getLogLevels"
});
};

})();
49 changes: 19 additions & 30 deletions src/capi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,46 +21,35 @@ class StackmatStateReceiver : public Receiver<StackmatState> {

};

struct FskubeState {
Demodulator demodulator;
Rs232Interpreter rs232Interpreter;
StackmatInterpreter stackmatInterpreter;
StackmatStateReceiver stackmatStateReceiver;
};
static Demodulator demodulator;
static Rs232Interpreter rs232Interpreter;
static StackmatInterpreter stackmatInterpreter;
static StackmatStateReceiver stackmatStateReceiver;
static bool initialized = false;

void* fskube_initialize(unsigned int sampleRate) {
FskubeState *fskube = new FskubeState();
void fskube_initialize(unsigned int sampleRate) {
FskParams fsk;
fsk.samplesPerSecond = sampleRate;
fsk.bitsPerSecond = 1200;
fsk.markFrequency = 1200;
fsk.spaceFrequency = 2200;
fskube->demodulator.setFskParams(fsk);
demodulator.setFskParams(fsk);

fskube->demodulator.connect(&fskube->rs232Interpreter);
fskube->rs232Interpreter.connect(&fskube->stackmatInterpreter);
fskube->stackmatInterpreter.connect(&fskube->stackmatStateReceiver);
demodulator.connect(&rs232Interpreter);
rs232Interpreter.connect(&stackmatInterpreter);
stackmatInterpreter.connect(&stackmatStateReceiver);

return fskube;
initialized = true;
}

void fskube_destroy(void *fskube_) {
FskubeState *fskube = static_cast<FskubeState*>(fskube_);
delete fskube;
bool fskube_addSample(double sample) {
assert(initialized);
stackmatStateReceiver.receivedSomething = false;
demodulator.receive(sample);
return stackmatStateReceiver.receivedSomething;
}

bool fskube_addSample(void *fskube_, double sample) {
FskubeState *fskube = static_cast<FskubeState*>(fskube_);

fskube->stackmatStateReceiver.receivedSomething = false;
fskube->demodulator.receive(sample);
return fskube->stackmatStateReceiver.receivedSomething;
}

StackmatState fskube_getState(void *fskube_) {
FskubeState *fskube = static_cast<FskubeState*>(fskube_);

assert(fskube->stackmatStateReceiver.receivedSomething);
fskube->stackmatStateReceiver.receivedSomething = false;
return fskube->stackmatStateReceiver.state;
StackmatState fskube_getState() {
assert(initialized);
return stackmatStateReceiver.state;
}
12 changes: 8 additions & 4 deletions src/capi.h
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#ifndef CAPI_H
#define CAPI_H

extern "C" {

#include "stackmat.h"

void *fskube_initialize();
// Note that this api only supports interpreting one stackmat at a time.
// This seems fine to me.
void fskube_initialize(unsigned int sampleRate);

void fskube_destroy(void *fskube);
bool fskube_addSample(double sample);

bool fskube_addSample(void *fskube);
fskube::StackmatState fskube_getState();

fskube::StackmatState fskube_getState(void * fskube);
}

#endif // CAPI_H
6 changes: 6 additions & 0 deletions src/embind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "rs232.h"
#include "stackmat.h"
#include "logging.h"
#include "capi.h"

using namespace emscripten;

Expand Down Expand Up @@ -50,6 +51,11 @@ EMSCRIPTEN_BINDINGS(my_module) {
.field("commandByte", &StackmatState::commandByte)
;

function("fskube_initialize", &fskube_initialize);
function("fskube_addSample", &fskube_addSample);
function("fskube_getState", &fskube_getState);


// TODO - the documentation https://github.com/kripken/emscripten/wiki/embind
// is missing the required argument to allow_subclass.
class_<Receiver<bool>>("boolReceiver")
Expand Down
35 changes: 35 additions & 0 deletions src/wrap_c.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,37 @@
// These are here instead of in embind.cpp because embind can't deal with char*,
// it can only handle std::string. We don't want to use std::string because it
// seriously bloats the size of the generated code.
Module.getLogLevels = Module.cwrap("getLogLevels", 'string');
Module.setLogLevels = Module.cwrap("setLogLevels", null, ['string']);

var methods = {
initialize: function(sampleRate) {
Module.fskube_initialize(sampleRate);
},
addSample: function(samples) {
for(var i = 0; i < samples.length; i++) {
var stateAvailable = Module.fskube_addSample(samples[i]);
if(stateAvailable) {
var state = Module.fskube_getState();
postMessage({
method: "newState",
args: [ state ]
});
}
}
},
setLogLevels: function(levels) {
Module.setLogLevels(levels);
},
getLogLevels: function() {
postMessage({
method: "getLogLevels",
args: [ Module.getLogLevels() ]
});
}
};

// TODO - for now we're assuming we're running in a webworker
this.onmessage = function(e) {
methods[e.data.method].apply(null, e.data.args);
};
23 changes: 23 additions & 0 deletions stackmat.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<html>
<head>
<script src="release/stackmat.js"></script>
<script>
(function() {
"use strict";

window.addEventListener("load", function() {
var timeArea = document.getElementById("timeArea");

Stackmat.initialize("release/fskube.js");
Stackmat.onstackmatstate = function(state) {
timeArea.innerHTML = state.millis;
};
});

})();
</script>
</head>
<body>
<div id="timeArea"></div>
</body>
</html>

0 comments on commit 08f9bd8

Please sign in to comment.