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