Skip to content

Instantly share code, notes, and snippets.

@peterc
Created February 27, 2012 00:32
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save peterc/1920095 to your computer and use it in GitHub Desktop.
Save peterc/1920095 to your computer and use it in GitHub Desktop.
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
@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
velocity += Math.floor(Math.random() * 20) - 10
velocity = 0 if velocity < 0
velocity = 127 if velocity > 127
console.log "#{note}\t#{("*" for x in [0..(velocity / 7)]).join('')}"
@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
lp = setInterval =>
@playStep @currentStep - 1
@currentStep++
@currentStep = 1 if @currentStep == @totalSteps + 1
clearInterval lp 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 = 96
# 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 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
@dominictarr
Copy link

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

@darscan
Copy link

darscan commented Jan 10, 2013

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