Skip to content

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Simple 16 step drum machine using CoffeeScript and Node
# Simple 16 step drum machine experiment with Node and CoffeeScript
# by Peter Cooper - @peterc
#
# Inspired by Giles Bowkett's screencast at
# http://gilesbowkett.blogspot.com/2012/02/making-music-with-javascript-is-easy.html
#
# Screencast demo of this code at http://www.youtube.com/watch?v=qWKkEaKL6DQ
#
# Required:
# node, npm and coffee-script installed
#
# To run:
# npm install midi
# run up Garageband
# create a software instrument (ideally a "drum kit")
# run this script with "coffee [filename]"
# ctrl+c to exit
# customize the drum steps at the footer of this file
#
# Yeah, not great OOD for real, but just a fun Sunday night
# project for now :-)
#
# P.S. This is my first CoffeeScript in > 1 year so I have NO IDEA
# if I'm even vaguely doing it right. But it works. Please leave
# comments with code improvements to help me learn!
class DrumMachine
constructor: (@midi) ->
@playing = false
@bpm = 160
@currentStep = 1
@steps = ([] for i in [0..15])
instruments:
kick: 36
hihat: 42
clap: 39
snare: 40
cowbell: 56
crash: 49
barDuration: ->
60000 / @bpm * 4
stopNote: (note) ->
@midi.sendMessage [128, note, 0]
playNote: (note, velocity, duration) ->
return unless @playing
console.log "#{note} : #{velocity} : #{duration}ms"
@midi.sendMessage [144, @instruments[note], velocity]
setTimeout =>
@stopNote note
, duration
playStep: (step) ->
@playNote(note[0], note[1], 40) for note in @steps[step]
play: ->
@playing = true
@loop = setInterval =>
@playStep @currentStep - 1
@currentStep++
@currentStep = 1 if @currentStep == 17
clearInterval @loop unless @playing
, this.barDuration() / 16
set: (instrument, steps, velocity = 127) ->
@steps[step - 1].push [instrument, velocity] for step in steps
pause: ->
@playing = false
stop: ->
@pause()
@currentStep = 1
@midi.sendMessage [252, 0, 0]
@midi.sendMessage [176, 123, 0]
# -----
midi = require 'midi'
midiOut = new midi.output
try
midiOut.openPort(0)
catch error
midiOut.openVirtualPort ''
dm = new DrumMachine(midiOut)
dm.bpm = 93
# dm.set <instrument>, <step nos> (, <velocity>)
dm.set 'hihat', [1, 3, 5, 7, 9, 11, 13, 15], 100
dm.set 'hihat', [2, 4, 6, 8, 10, 12, 14, 16], 39
dm.set 'kick', [1, 4, 7, 11]
dm.set 'snare', [5, 13]
# extra bits added for fun
dm.set 'cowbell', [2, 4, 7, 12], 20
dm.set 'cowbell', [3, 5, 8, 13], 80
dm.set 'crash', [15], 20
dm.set 'clap', [5, 13], 80
dm.set 'clap', [16], 50
dm.play()
process.addListener "SIGTERM", ->
dm.stop
midiOut.closePort()
@dominictarr

hey, did you have any problems with timing in this?

is setTimeout tight enough?
I've been looking at in browser js drum machines but the timing is too sloppy.

@peterc
Owner

In Node it's very acceptable. In the browser, yeah.. timers can be pretty bad. There must be a library aimed at solving this, but an idea that comes to mind is to run a timer at, say, 10 times the resolution and then manually check when the time is right to trigger something. I think it should perform well and be a lot tighter.

@dominictarr

good to hear! I'll be experimenting with that approach in the browser shortly. it's mid-high on my to do list.

@darscan

I think stopNote should be like so

@midi.sendMessage [128, @instruments[note], 0]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.