public
Last active

Simple 16 step drum machine using CoffeeScript and Node

  • Download Gist
16step.coffee
CoffeeScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
# 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
@totalSteps = 16 # changeable for different time signatures
@steps = ([] for i in [1..64])
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 == @totalSteps + 1
clearInterval @loop unless @playing
, this.barDuration() / @totalSteps
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 = 120
 
# 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()
watchr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# watchr file to live restart the drum machine when changes
# are made to the code. Perhaps there's a better way of doing
# this.. let me know!
#
# To use, you need Ruby, gem install watchr, and run
# watchr <filename of this>
 
$running_pid = nil
 
watch('.*\.coffee') do |md|
Process.kill("INT", $running_pid) if $running_pid
 
$running_pid = fork do
exec("coffee", md[0])
end
end

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.

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.

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

I think stopNote should be like so

@midi.sendMessage [128, @instruments[note], 0]

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.