Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.
For the best experience please use the latest Chrome, Safari or Firefox browser.
Making Music with Python and BeatLounge
April 12, 2012 @ PyATL
Drew Smathers
IRC/twitter/github/launchpad: djfroofy
Computer Music
What this talk is mostly about ...
...with Python of course
Computer Music Languages and Live Coding Software
Making a Sound with Python
Recv/Send Music Data with Python
MIDI Overview
Focus: Communicating music events including pitch, velocity and control signals
Around since the 80s ... (note range: [0,127])
0=C, 1=C#, 2=D, ..., 11=B
Use NOTE % 12 to compute octave
NOTEON [0,127] [0,127]
NOTEOFF [0,127]
A Note on Notes
MIDI can be used arbitrarily but more often than not application is for 12TET music. Begin with C=0. A half step is 1.
C, Cs, D, Eb, E, F, Fs, G, Gs, A, As, B = range(12)
chromatic_scale = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
major_scale = [C, D, E, F, G, A, B]
minor_scale = aeolian = [C, D, Eb, F, G, Gs, As]
# raise an octave
notes = [note + 12 for note in major_scale]
A Note on Notes (continued)
A chord is typically a triads (3 notes). These are fun to play with. You can sound them at the same time or play in sequence (arpeggio).
from itertools import combinations
C, Cs, D, Eb, E, F, Fs, G, Gs, A, As, B = range(12)
major_scale = [C, D, E, F, G, A, B]
all_triads = combinations(major_scale, 3)
Fluidsynth
Provides a MIDI interfaces for playing sound fonts
import time
from fluidsynth import Synth
fs = Synth()
fs.start('coreaudio')
sfid = fs.sfload("example.sf2")
track = 0
fs.program_select(track, sfid, 0, 0)
# Play some notes
fs.noteon(track, 60, 127)
time.sleep(1)
fs.noteoff(track, 60)
fs.noteon(track, 64, 127)
BeatLounge (bl.instrument.fsynth)
Wraps pyfluidsynth. Some C-ish APIs hidden: sfload(), program_select(), track etc
from bl.instrument.fsynth import Instrument
instr = Instrument('example.sf2', connection='mono')
instr.playnote(60, 127)
time.sleep(1)
instr.stopnote(60)
instr.playnote(64, 127)
BeatLounge (bl.comps.midiexample)
...
from bl.midi import MonitorHandler, MidiDispatcher
from bl.midi import getInput, getOutput, init
init()
midi_in = getInput('LPK25')
midi_out = getOutput('LPK25')
handler = MonitorHandler({1:instr})
disp = MidiDispatcher(midi_in, [handler])
disp.start()
sender = ClockSender(midi_out)
sender.start()
"Strongly Timed"
Terminology originated with ChucK. Strongly timed computer music systems provide a "consistent, sample-synchronous view of time" ... "embeds timing control directly in the code"
-- http://soundlab.cs.princeton.edu/publications/chuck_sigmm2004.pdf"Strongly Timed" Pop Quiz
Question: Is the following system strongly-timed?
import time
tick = 60. / 130. / 24.
def my_music_player(instruments, schedules):
while True:
time.sleep(tick)
for (instr, schedule) in zip(instruments, schedules):
(t, command, args) = schedule[0]
if time.time() == t:
schedule.pop(0)
if command == 'noteon':
instr.playnote(*args)
elif command == 'noteoff':
instr.stopnote(*args)
"Strongly Timed" Pop Quiz (continued)
Answer: NO ... So many problems
# tick + <op time> + <preemption time>
while True:
time.sleep(tick)
# <preemption time>
for (instr, schedule) in zip(instruments, schedules):
# <op time> + <preemption time>
(t, command, args) = schedule[0]
# Equality may never pass - we might end up "ahead"
# of t forever. So ... this is not sample-synchronous.
if time.time() == t:
# ...
Is BeatLounge Strongly Timed?
Answer: ALMOST ... So many problems
Systems like ChucK are strongly timed and support for very high sample rates. BeatLounge (or CPython, really) is "good enough" for MIDI sequencing but not fast enough for DSP. <Insert Actual Metrics>
BeatLounge provides sample synchronous view of time: clock.ticks vs time.time()
bl.scheduler.BeatClock - Isochronous scheduler
bl.scheduler.ScheduledEvent
# a song.py
...
from itertools import cycle
import random
notes, octaves = [60, 64, 67], [-12, 0, 12]
velocity = cycle([127,80,120,90])
def play_instr():
note = random.choice(notes) + random.choice(octaves)
clock.callLater(18, instr.stopnote, note)
instr.playnote(note, velocity.next())
event = clock.schedule(play_instr)
# Start on the next measure, repeat call every 1/8 note
event.startAfter((1,1), (1,8))
...
BeatLounge "Live Coding": bl.console
$ python bl/console.py >>> from tutor import song1 >>> song1.event.stopAfter((4,1))
Hint: Loading soundfonts is very expensive. So ... load all resources and initialize before starting (not during) performance.
bl.console (continued)
$ python bl/console.py --help
Usage: console.py [options]
Options:
-c, --channels= Number of channels or a label: stereo,
mono, quad [default: stereo]
-l, --logfile= Path to logfile [default: child.log]
-b, --bpm= The tempo in beats per minute [default: 130]
-t, --tpb= Ticks per beat [default: 24]
-m, --meter= The meter (default 4/4) [default: 4/4]
--version Display Twisted version and exit.
--help Display this help and exit.
bl.console (continued)Audio device can be specified as well:
$ python bl/console.py -m 3/4 -b 150 jack
"Arpeggiator"
Popular feature on early synthesizers. Take notes held down to form arpeggio: sequencing of notes.
Today: software arpegiators. Play a sequence of notes: up, down, up + down, random, in order, ...
Different algorithms can be layered: play X (up, down, ...) over N octaves
Arpeggiators in BeatLounge (bl.player)
Arpeggiators in BeatLounge (bl.player)
...
from bl.player import Player
notes = cycle([60, 64, 67, 64]).next
velocity = cycle([127,90,120,80]).next
dtt = clock.meter.divisionToTicks
player = Player(instr, notes, velocity,
stop=12, interval=dtt(1,8))
player.startPlaying()
Arpeggiators in BeatLounge (bl.arp)
Arpeggiators in BeatLounge (bl.arp)
...
from bl.arp import Adder, OrderedArp,
notes = Adder(OctaveArp(RandomArp([0, 4, 7, 9]))
notes.amount = 36
velocity = OrderedArp([127,90,110,80,120,127,90,100])
dtt = clock.meter.divisionToTicks
player = Player(instr, notes, velocity,
stop=12, interval=dtt(1,8))
player.startPlaying()
"Rudiments"
"... one of the basic patterns used in rudimental drumming. These patterns of drum strokes can be combined in many ways to create music."
--http://en.wikipedia.org/wiki/Drum_rudiment
Examples: Five Stroke Roll, Six Stroke Roll, Paradiddle, etc.
Components: Strokes ([R,L,R,L,R,R]) + Timing
Rudiments in BeatLounge
from bl.rudiments import FiveStrokeRoll
fsr = FiveStrokeRoll()
drum_player = RudimentSchedulePlayer(drum, fsr, 35, 25)
# RudimentSchedulePlayer first gets the stroke from the rudiment:
list(fsr.strokes(35, 25)) = [35, 35, 25, 25, 35,
25, 25, 35, 35, 25]
# ... then a cycle over the time values
list(fsr.time()) == [0, 6, 12, 18, 24,
48, 54, 60, 66, 72]
Actors
def change_phrase(arp, phrases):
for i in range(128):
phrase = random.choice(phrases[i:])
for j in range(8):
phrase2 = []
for (i1, i2) in zip([phrase[j]] * 4 + [phrase[-j]] * 4, phrase):
phrase2.extend([i1,i2])
arp.reset(phrase2[:8] + phrase + phrase2[8:] + phrase)
yield
actor = clock.schedule(change_phrase(arp, phrases).next)
actor.startAfter((1,1), (4,1))
Demo Time
Links
BeatLounge: https://github.com/djfroofy/beatlounge
pyfluidsynth: http://code.google.com/p/pyfluidsynth
pyPortMidi: http://alumni.media.mit.edu/~harrison/code.html
Use a spacebar or arrow keys to navigate