Last active
September 21, 2016 14:45
-
-
Save hughrawlinson/5a8e4229dfd33d799d098448530ae1bc to your computer and use it in GitHub Desktop.
An implementation of PSK31 encoding in Web Audio
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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