Last active
September 23, 2020 07:02
-
-
Save sebm/cbd64e30e21edd7049a9e5a7cd62a2fe to your computer and use it in GitHub Desktop.
Render a metronome mp3 in response to an HTTP request
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
// Google Cloud Source repos don't allow world-readable so this is the next best thing | |
// this is the code as of SHA b34717ce19fa9da940333402e7534c0e99f687a3 | |
const synth = require("synth-js"); | |
const MidiWriter = require("midi-writer-js"); | |
const ffmpeg = require("fluent-ffmpeg"); | |
const { Readable } = require("stream"); | |
const concat = require("concat-stream"); | |
const MEASURES = 100; | |
const THE_ONE = { pitch: ["A6"], duration: "16" }; | |
const TWO_THREE_FOUR = { | |
pitch: ["A4"], | |
duration: "16", | |
wait: ["16", "16", "16"], | |
repeat: "3", | |
}; | |
const MAX_BPM = 400; | |
function validateRequest(req) { | |
const { bpm } = req.query; | |
if (!bpm) { | |
throw new Error("bpm param must be set"); | |
} | |
if (Number.isNaN(Number.parseInt(bpm))) { | |
throw new Error("bpm must be a number"); | |
} | |
if (bpm > MAX_BPM) { | |
throw new Error(`bpm must be less than ${MAX_BPM}`); | |
} | |
if (bpm < 40) { | |
throw new Error("bpm must be less than 40"); | |
} | |
} | |
exports.metronome = (req, res) => { | |
try { | |
validateRequest(req); | |
// Start with a new track | |
const track = new MidiWriter.Track(); | |
const { bpm } = req.query; | |
// Define an instrument (optional): | |
track.addEvent(new MidiWriter.ProgramChangeEvent({ instrument: 1 })); | |
track.setTempo(bpm); | |
track.addEvent( | |
[ | |
new MidiWriter.NoteEvent(THE_ONE), | |
new MidiWriter.NoteEvent(TWO_THREE_FOUR), | |
], | |
() => ({ sequential: true }) | |
); | |
for (let x = 0; x < MEASURES; x++) { | |
track.addEvent( | |
[ | |
new MidiWriter.NoteEvent({ ...THE_ONE, wait: ["16", "16", "16"] }), | |
new MidiWriter.NoteEvent(TWO_THREE_FOUR), | |
], | |
() => ({ sequential: true }) | |
); | |
} | |
const midiWriter = new MidiWriter.Writer(track); | |
const midiBuffer = new Buffer.from(midiWriter.buildFile()); | |
const wavBuffer = synth.midiToWav(midiBuffer).toBuffer(); | |
const metronomeWavStream = Readable.from(wavBuffer); | |
const filename = `${bpm}.mp3`; | |
res.setHeader("Content-disposition", "attachment; filename=" + filename); | |
res.contentType("mp3"); | |
const concatStream = concat((buffer) => { | |
res.send(buffer); | |
res.end(); | |
}); | |
ffmpeg() | |
.format("mp3") | |
.audioCodec("libmp3lame") | |
.audioBitrate(8) | |
.audioFrequency(12000) | |
.input(metronomeWavStream) | |
.pipe(concatStream); | |
} catch (err) { | |
res.status(500).send(err.message); | |
} | |
}; |
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
exports.handler = function(context, event, callback) { | |
const twiml = new Twilio.twiml.VoiceResponse(); | |
const { bpm } = event; | |
twiml.play(`💃.cloudfunctions.net/metronome?bpm=${bpm}`) | |
return callback(null, twiml); | |
}; |
Author
sebm
commented
Sep 23, 2020
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment