For this notebook, you will need a Phoenix box with a speaker connected to PWG / GND, and optionally an LED at Digital Output D3 / GND for some pointless flashing lights.
import os, sys, time
from IPython.display import display, Javascript, HTML
import phm
p = phm.phm()
Notes table from here
import pandas
notes_csv = pandas.read_csv('notes.csv', sep='\t')
notes = {}
for note, freq in zip(notes_csv.name, notes_csv.frequency):
notes[note] = freq
notes_csv[:4]
A global variable setting the tempo in bpm
tempo = 160
Some utility functions for playing notes / rests on the Phoenix
shutup = lambda : p.set_frequency(0)
def play_note(name, beats=1):
display(Javascript("$('div#%s').addClass('playing');" % name))
play_frequency(notes[name], beats)
display(Javascript("$('div#%s').removeClass('playing');" % name))
def play_frequency(freq, beats=1):
mask = int(freq) % 15
p.write_outputs(mask)
p.set_frequency(freq)
time.sleep(beats * 60./tempo)
p.set_frequency(0)
p.write_outputs(0b0000)
def rest(beats=1):
time.sleep(beats * 60. / tempo)
def shutup_handler(self, etype, value, tb, tb_offset=None):
shutup()
print 'KeyboardInterrupt'
get_ipython().set_custom_exc((KeyboardInterrupt,), shutup_handler)
Now we can play simple notes by name, or specific frequencies
play_note('C4')
play_frequency(400)
Here is a class for representing a note. This is just a simple wrapper for a name and a frequency, which will play its note when you call it.
class Note(object):
name = None
frequency = 0
def __init__(self, name, freq):
self.name = name
self.frequency = freq
def __hash__(self):
return self.freq
def __call__(self, beats=1):
play_frequency(self.frequency, beats)
Now assign all of these notes to variables, so that our global namespace is filled with callable notes
g = globals()
for name, freq in notes.items():
g[name] = Note(name, freq)
So we can just call note names to play notes
C4()
C4()
D4()
E4()
E4()
Now a function that parses a string into a sequence of notes and their durations.
def play_tune(tune_s, octave=4):
notelist = tune_s.split()
for note in notelist:
if ':' in note:
note, beats = note.split(':')
beats = float(beats)
else:
beats = 1
if note == 'r':
rest(beats)
continue
if note[-1] == '-':
note = note[:-1] + str(octave-1)
elif note[-1] == '+':
note = note[:-1] + str(octave+1)
elif not note[-1].isdigit():
note = note + str(octave)
play_note(note, beats)
With this, we can write a simple version of Ode to Joy in a few lines of Python.
tempo = 160
bar1 = 'E E F G G F E D C C D E E:1.5 D:.5 D:2'
bar2 = 'E E F G G F E D C C D E D:1.5 C:.5 C:2'
bar3 = 'D D E C D E:.5 F:.5 E C D E:.5 F:.5 E D C D G-:2'
play_tune(bar1)
play_tune(bar2)
play_tune(bar3)
Now that we have written simple Python to play some music, we can actually hack a little HTML and Javascript to build an actual playable keyboard in the notebook.
The notes are played on the phoenix by the same mechanism as before, just by registering javascript callbacks to the Python kernel when you click on the HTML elements.
Build the keyboard in HTML with divs and ids
key_t = "<div class='key' id={0}><span class='keyname'>{0}</span></div>"
black_key_t = "<div class='key sharp {1}' id={0}></div>"
names = []
for o in range(8):
names.extend([ '%s%i' % (k,o) for k in 'CDEFGAB'])
keys = []
for name in names:
keys.append(key_t.format(name))
if name.startswith(tuple('ACDFG')):
sharpname = name[0] + 's' + name[1:]
keys.append(black_key_t.format(sharpname, sharpname[:2]))
print '\n'.join(keys[:10])
Some CSS for the keyboard (edit this cell to see it)
And let's publish that HTML to the notebook, to see how we've done.
display(HTML("""<div class='keyboard-wrapper'>
<div class='keyboard'>
%s
</div>
</div>""" % '\n'.join(keys)))
Finally, register mouse events on the keys that will make the appropriate Python calls when you click on each key.
%%javascript
var myState = {};
myState.mousedown = false;
$(document).on("mousedown", function () { myState.mousedown = true; }
).on("mouseup", function () { myState.mousedown = false; });
var mousedown = function(e) {
$(this).addClass("playing");
IPython.notebook.kernel.execute("p.set_frequency(notes['" + $(this)[0].id + "'])");
}
var mouseenter = function(e) {
if ( !myState.mousedown ) return;
$(this).addClass("playing");
IPython.notebook.kernel.execute("p.set_frequency(notes['" + $(this)[0].id + "'])");
}
var shutup = function(e) {
if ( ! $(this).hasClass("playing") ) return ;
$(this).removeClass("playing");
IPython.notebook.kernel.execute("p.set_frequency(0)");
}
$("div.key").on("mousedown", mousedown
).on("mouseenter", mouseenter
).on("selectstart", false
).on("mouseup", shutup
).on("mouseout", shutup);
import numpy as np
tempo = 680
play_tune('E E r E r C E r G r:4 G- r:4', octave=4)
for i in range(2):
for i in range(2):
play_tune('C r:2 G- r:2 E- r:3 A- r B- r As- A- r')
play_tune('C E r G A r F G r E r C D B- r:4')
for i in range(2):
play_tune('G Fs F Ds r E r Gs- A- C r A- C D r:2')
play_tune('G Fs F Ds r E r C+ r C+ C+ r:4')
play_tune('G Fs F Ds r E r Gs- A- C r A- C D r:2 Eb r:2 D r:2 C r:4')
play_tune('C C r C r C D r E C r A- G- r:4')
play_tune('C C r C r C D r E r:8')
play_tune('C C r C r C D r E C r A- G- r:4')
play_tune('E E r E r C E r G r:4 G-:1.5 r:4')
play_tune('C r:2 G- r:2 E- r:3 A- r B- r As- A- r')
play_tune('C E r G A r F G r E r C D B- r:4')
for i in range(2):
play_tune('E C r G- r:2 Gs- r A- F r F A- r:4')
play_tune('D A:1.5 A A G F r:0.5 E C r:.5 A- G- r:4')
play_tune('E C r G- r:2 Gs- r A- F r F A- r:4')
play_tune('B- F F F E D C C- r C- D2 r:4')