Skip to content

Instantly share code, notes, and snippets.

@hughrawlinson
Last active September 21, 2016 14:45
Show Gist options
  • Save hughrawlinson/5a8e4229dfd33d799d098448530ae1bc to your computer and use it in GitHub Desktop.
Save hughrawlinson/5a8e4229dfd33d799d098448530ae1bc to your computer and use it in GitHub Desktop.
An implementation of PSK31 encoding in Web Audio
const ENERGY_PER_BIT = 100;
const CARRIER_FREQUENCY = 440;
const PAYLOAD = 'Test Payload';
const getAsciiArrayFromChar = (character) => (character.charCodeAt(0) >>> 0).toString(2).split('');
// Parameter names come from the equation for BPSK here:
// https://en.m.wikipedia.org/wiki/Phase-shift_keying#Binary_phase-shift_keying_.28BPSK.29
const binaryPhaseShiftKeyingOscillator = (
t,
a,
carrierFrequency,
bit) => a * Math.cos(2*Math.PI*carrierFrequency*t+Math.PI*(1-bit));
const prepareBinaryPhaseShiftKeyingOscillator = (
energyPerBit,
symbolDuration,
carrierFrequency) => {
const a = Math.sqrt(2*energyPerBit/symbolDuration);
return (t,bit) => {
return binaryPhaseShiftKeyingOscillator(
t,
a,
carrierFrequency,
bit);
}
};
(()=>{
// According to https://en.m.wikipedia.org/wiki/PSK31#, 8000hz is a common sample rate. It happens to
// work well with PSK31, but feel free to adjust it, it shouldn't break much.
// UPDATE: That didn't work because the web audio api requires sample rate to be a power of 2
// const BPSK31_SAMPLE_RATE = 8000;
// switching to 44100
const BPSK31_SAMPLE_RATE = 44100;
const baudRate = 31.25;
// in samples
const bitDuration = BPSK31_SAMPLE_RATE/baudRate;
const asciiBinaryPayload = Array.prototype.map.call(PAYLOAD,
getAsciiArrayFromChar)
.reduce((acc,character) => acc.concat(character));
const messageDuration = bitDuration * asciiBinaryPayload.length;
const bpsk31 = prepareBinaryPhaseShiftKeyingOscillator(ENERGY_PER_BIT, bitDuration, CARRIER_FREQUENCY);
const ctx = new AudioContext(1,messageDuration,BPSK31_SAMPLE_RATE);
// ScriptProcessorNodes are deprecated, but their replacement hasn't been implemented anywhere yet.
const spn = ctx.createScriptProcessor(16384,1,1);
spn.onaudioprocess = function(audioProcessingEvent) {
// We have to write into this reference. It sucks.
var outputData = audioProcessingEvent.outputBuffer.getChannelData(0);
for (var sample = 0; sample < outputData.length; sample++) {
const t = audioProcessingEvent.playbackTime+(sample/ctx.sampleRate);
const i = asciiBinaryPayload[Math.floor(t/messageDuration)]
const result = bpsk31(
t,
i);
outputData[sample] = result;
}
}
spn.connect(ctx.destination);
// ctx.oncomplete = (offlineAudioCompletionEvent) => {
// const bpsk31EncodedPayloadAudioBuffer = offlineAudioCompletionEvent.renderedBuffer;
// console.log(bpsk31EncodedPayloadAudioBuffer.getChannelData(0).filter((a)=>a!=0));
// const onlineCtx = new AudioContext();
// const source = onlineCtx.createBufferSource();
// source.buffer = bpsk31EncodedPayloadAudioBuffer;
// source.connect(onlineCtx.destination);
// source.start();
// }
// ctx.startRendering();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment