Speaking for the Dead with Twilio, Netlify, Pusher, and Web Audio for Trivium

What Do The Dead Men Say?

Image for post
Image for post
What the dead men say, it’s just between us…

Dead Voicemail with Twilio

Web app
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
twiml.say("What do the Dead Men Say?"); twiml.record({
action: NETLIFY_FUNCTION_URL,
method: "GET",
timeout: 5,
maxLength: 5
});
callback(null, twiml);
};

Channel Recording to Client

const Pusher = require('pusher')let pusher = new Pusher({
appId: process.env.PUSHER_APP_ID,
key: process.env.PUSHER_KEY,
secret: process.env.PUSHER_SECRET,
cluster: 'us2',
encrypted: true
})
const triggerEvent = (message) => {
return new Promise((resolve, revoke) => {
pusher.trigger(
'trivium',
'new-recording',
message,
(err, req, res) => {
if (err) {
revoke(err)
}
resolve()
}
);
})
}
exports.handler = async (event, context) => {
let message = {
"url": `${event.queryStringParameters['RecordingUrl']}.mp3`
}
try{
await triggerEvent(message)
return {
statusCode: 200,
headers: {
'Content-Type': 'application/xml'
},
body: '<?xml version="1.0" encoding="UTF-8"?><Response><Hangup/></Response>'
}
} catch (err) {
return {
statusCode: 500,
body: err.toString()
}
}
}

Transform and Playback Recording

Youtube Stream
let pusher = new Pusher(process.env.PUSHER_KEY, {
cluster: 'us2',
forceTLS: true
})
let channel = pusher.subscribe('trivium')channel.bind('new-recording', (data) => {
recordingQueue.push(data)
})
recordingQueue = queue(asyncify(async (task, callback) => {
await playRecording(task.url)
callback()
}), 5);
let AudioContext = window.AudioContext || window.webkitAudioContextlet context = new AudioContext()
function reverseRecording(audioBuffer) {
let reversedAudioBuffer = context.createBuffer(
audioBuffer.numberOfChannels,
audioBuffer.length,
audioBuffer.sampleRate
)
for (let i = 0; i < audioBuffer.numberOfChannels; i++) {
reversedAudioBuffer.copyToChannel(audioBuffer.getChannelData(i), i);
}
for (let i = 0; i < reversedAudioBuffer.numberOfChannels; i++) {
reversedAudioBuffer.getChannelData(i).reverse();
}
return reversedAudioBuffer
}
async function transformRecording(audioBuffer) {
let context = new OfflineAudioContext(
audioBuffer.numberOfChannels,
audioBuffer.length,
audioBuffer.sampleRate
)
let source = context.createBufferSource()
source.buffer = reversedAudioBuffer
let convolver = context.createConvolver() convolver.buffer = await context.decodeAudioData(
await (await fetch(IMPULSE_URL)).arrayBuffer()
)
let outCompressor = context.createDynamicsCompressor() source.connect(convolver)
convolver.connect(outCompressor)
let dryGain = context.createGain()
dryGain.gain.value = 0.5
source.connect(dryGain)
dryGain.connect(outCompressor)
outCompressor.connect(context.destination)
source.start(0) return this.reverseRecording(await context.startRendering())
}
async function playRecording(url) {
try {
let recording = await fetch(url)
let arrayBuffer = await recording.arrayBuffer() let audioBuffer = await context.decodeAudioData(arrayBuffer) audioBuffer = await transformRecording(audioBuffer) let source = context.createBufferSource() source.buffer = audioBuffer
source.connect(context.destination)
source.start()
await new Promise(resolve => source.onended = resolve)
} catch (e) {
// console.log(e)
}
}

Thanks

Image for post
Image for post

Written by

I develop websites for rock 'n' roll bands and get paid in sex and drugs. Previously Silva Artist Management, SoundCloud, and Songkick. Currently: Available

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store