diff --git a/examples/ex-basics.py b/examples/ex-basics.py
index 7287028..3929049 100755
--- a/examples/ex-basics.py
+++ b/examples/ex-basics.py
@@ -1,30 +1,29 @@
#!/usr/bin/python
-
-from isobar import *
+import isobar as iso
# create a repeating sequence with scalar transposition:
# [ 36, 38, 43, 39, ... ]
-a = PSeq([ 0, 2, 7, 3 ]) + 36
+a = iso.PSeq([0, 2, 7, 3]) + 36
# apply pattern-wise transposition
# [ 36, 50, 43, 51, ... ]
-a = a + PSeq([ 0, 12 ])
+a = a + iso.PSeq([0, 12])
# create a geometric chromatic series, repeated back and forth
-b = PSeries(0, 1, 12) + 72
-b = PPingPong(b)
-b = PLoop(b)
+b = iso.PSeries(0, 1, 12) + 72
+b = iso.PPingPong(b)
+b = iso.PLoop(b)
# create an velocity series, with emphasis every 4th note,
# plus a random walk to create gradual dynamic changes
-amp = PSeq([ 50, 35, 25, 35 ]) + PBrown(0, 1, -20, 20)
+amp = iso.PSeq([50, 35, 25, 35]) + iso.PBrown(0, 1, -20, 20)
# a Timeline schedules events at a given BPM.
# by default, send these over the first MIDI output.
-timeline = Timeline(120, debug = True)
+output_device = iso.io.midi.MidiOut("IAC Driver IAC Bus 1")
+timeline = iso.Timeline(120, device=output_device, debug=True)
# assign each of our Patterns to particular properties
-timeline.sched({ 'note': a, 'dur': 1, 'gate': 2 })
-timeline.sched({ 'note': b, 'dur': 0.25, 'amp': amp })
+timeline.sched({'note': a, 'dur': 1, 'gate': 2})
timeline.run()
diff --git a/examples/ex-lsystem-grapher.py b/examples/ex-lsystem-grapher.py
index a22b293..32687b0 100755
--- a/examples/ex-lsystem-grapher.py
+++ b/examples/ex-lsystem-grapher.py
@@ -9,11 +9,11 @@
import random
import time
-seq = PLSys("N[+N+N]?N[-N]+N", depth = 3)
+seq = PLSys("N[+N+N]?N[-N]+N", depth=3)
notes = seq.all()
note_min = min(notes)
note_max = max(notes)
for note in seq:
- note = note - note_min
- print "#" * note
+ note = note - note_min
+ print "#" * note
diff --git a/examples/ex-lsystem-rhythm.py b/examples/ex-lsystem-rhythm.py
index 572f498..5f754e7 100755
--- a/examples/ex-lsystem-rhythm.py
+++ b/examples/ex-lsystem-rhythm.py
@@ -18,12 +18,12 @@
# - = transpose down one semitone
# [ = enter recursive branch
# ] = leave recursive branch
-notes = PLSys("N+[+N+N--N+N]+N[++N]", depth = 4)
+notes = PLSys("N+[+N+N--N+N]+N[++N]", depth=4)
notes = notes + 60
# use another l-system to generate time intervals.
# take absolute values so that intervals are always positive.
-times = PLSys("[N+[NN]-N+N]+N-N+N", depth = 3)
+times = PLSys("[N+[NN]-N+N]+N-N+N", depth=3)
times = PAbs(PDiff(times)) * 0.25
# delay = PDelay(notes, times)
@@ -32,5 +32,5 @@
velocity = PAbs(velocity)
timeline = Timeline(120)
-timeline.sched({ 'note' : notes, 'amp' : velocity, 'dur' : times })
+timeline.sched({'note': notes, 'amp': velocity, 'dur': times})
timeline.run()
diff --git a/examples/ex-lsystem-stochastic.py b/examples/ex-lsystem-stochastic.py
index eeecabe..006bbb7 100755
--- a/examples/ex-lsystem-stochastic.py
+++ b/examples/ex-lsystem-stochastic.py
@@ -10,7 +10,7 @@
import random
import time
-notes = PLSys("N[+N--?N]+N[+?N]", depth = 4)
+notes = PLSys("N[+N--?N]+N[+?N]", depth=4)
notes = PDegree(notes, Scale.majorPenta)
notes = notes % 36 + 52
@@ -19,5 +19,5 @@
timeline = Timeline(120)
timeline.output(midi)
-timeline.sched({ 'note': notes, 'dur': 0.25 })
+timeline.sched({'note': notes, 'dur': 0.25})
timeline.run()
diff --git a/examples/ex-markov-learner.py b/examples/ex-markov-learner.py
index efbb0a9..051aeb8 100755
--- a/examples/ex-markov-learner.py
+++ b/examples/ex-markov-learner.py
@@ -2,7 +2,7 @@
# parallel markov chain learner:
# 1. takes MIDI input, and constructs three markov chains for pitch, duration
-# and amplitude.
+# and amplitude.
# 2. after receiving a low "C", plays back melodies which are statistically
# similar to the input.
# after francois pachet's "continuator".
@@ -12,7 +12,7 @@
import time
-m_in = MidiIn()
+m_in = MidiIn()
m_out = MidiOut()
learner = MarkovLParallel(3)
@@ -29,7 +29,7 @@
dur = clock - clock0
dur = round(dur, 2)
- learner.register([ note.midinote, round(note.velocity, -1), dur ])
+ learner.register([note.midinote, round(note.velocity, -1), dur])
clock0 = clock
print "----------------------------------------------------"
@@ -38,12 +38,11 @@
chains = learner.chains()
pitch = PMarkov(chains[0])
-amp = PMarkov(chains[1])
-dur = PMarkov(chains[2])
+amp = PMarkov(chains[1])
+dur = PMarkov(chains[2])
t = Timeline(120, m_out)
print " - nodes: %s" % p.markov.nodes
print " - edges: %s" % p.markov.edges
-t.sched({ 'note': pitch, 'dur': dur, 'amp': amp, 'channel': 0 })
+t.sched({'note': pitch, 'dur': dur, 'amp': amp, 'channel': 0})
t.run()
-
diff --git a/examples/ex-permut-degree.py b/examples/ex-permut-degree.py
index f0d54e0..2b79e22 100755
--- a/examples/ex-permut-degree.py
+++ b/examples/ex-permut-degree.py
@@ -5,23 +5,35 @@
import random
# create a pitch line comprised of multiple permutations on a pelog scale
-ppitch = PShuffle([ random.randint(-6, 6) for n in range(6) ])
+ppitch = PShuffle([random.randint(-6, 6) for n in range(6)])
ppitch = PPermut(ppitch)
ppitch = PDegree(ppitch, Key("F#", "pelog"))
# create permuted sets of durations and amplitudes
# different lengths mean poly-combinations
-pdur = PShuffle([ 1, 1, 2, 2, 4 ], 1)
-pdur = PPermut(pdur) * 0.25
+pdur = PShuffle([1, 1, 2, 2, 4], 1)
+pdur = PPermut(pdur) * 0.25
-pamp = PShuffle([ 10, 15, 20, 35 ], 2)
-pamp = PPermut(pamp)
+pamp = PShuffle([10, 15, 20, 35], 2)
+pamp = PPermut(pamp)
# schedule on a 60bpm timeline and send to MIDI output
timeline = Timeline(60)
-timeline.sched({ 'note': ppitch + 60, 'dur': pdur, 'channel': 0, 'gate': 1, 'amp': pamp })
-timeline.sched({ 'note': ppitch + 24, 'dur': pdur * 4, 'channel': 1, 'gate': 2, 'amp': pamp })
-timeline.sched({ 'note': ppitch + 72, 'dur': pdur / 2, 'channel': 1, 'gate': 1, 'amp': pamp / 2 })
+timeline.sched({'note': ppitch + 60,
+ 'dur': pdur,
+ 'channel': 0,
+ 'gate': 1,
+ 'amp': pamp})
+timeline.sched({'note': ppitch + 24,
+ 'dur': pdur * 4,
+ 'channel': 1,
+ 'gate': 2,
+ 'amp': pamp})
+timeline.sched({'note': ppitch + 72,
+ 'dur': pdur / 2,
+ 'channel': 1,
+ 'gate': 1,
+ 'amp': pamp / 2})
# add some continuous warping
warp = PWRamp(2, PBrown(-0.1, 0.01, -0.15, -0.05))
diff --git a/examples/ex-piano-phase.py b/examples/ex-piano-phase.py
index fd276ac..1e9d67f 100755
--- a/examples/ex-piano-phase.py
+++ b/examples/ex-piano-phase.py
@@ -9,7 +9,7 @@
from isobar import *
# melody line
-seq = PSeq([ -7, -5, 0, 2, 3, -5, -7, 2, 0, -5, 3, 2 ])
+seq = PSeq([-7, -5, 0, 2, 3, -5, -7, 2, 0, -5, 3, 2])
# create a timeline at 160BPM
timeline = Timeline(160)
@@ -17,9 +17,12 @@
# schedule two identical melodies.
# we must copy the note sequence or else the position will be stepped
# by two every note... try removing the .copy() and see what happens!
-timeline.sched({ 'note': seq.copy() + 60, 'dur': 0.5 })
-timeline.sched({ 'note': seq.copy() + 72, 'dur': 0.5 * 1.01 })
+timeline.sched({'note': seq.copy() + 60, 'dur': 0.5})
+timeline.sched({'note': seq.copy() + 72, 'dur': 0.5 * 1.01})
# start playing, and block forever.
# alternatively, use timeline.background() to retain foreground control.
-timeline.run()
+timeline.background()
+
+while True:
+ pass
diff --git a/examples/ex-subsequence.py b/examples/ex-subsequence.py
index 605d361..367d7c7 100755
--- a/examples/ex-subsequence.py
+++ b/examples/ex-subsequence.py
@@ -2,9 +2,9 @@
from isobar import *
-scale = Scale([ 0, 2, 3, 6, 7 ])
+scale = Scale([0, 2, 3, 6, 7])
scale = Scale.pelog
-seq = PDegree(PBrown(0, 3, -12, 12, repeats = False), scale)
+seq = PDegree(PBrown(0, 3, -12, 12, repeats=False), scale)
offset = PStutter(PWhite(0, 4), 2)
seq = PSubsequence(seq, offset, 4)
@@ -12,14 +12,19 @@
seq = seq + 64
seq = PReset(seq, PImpulse(24))
-amp = PSeq([ 45, 35, 25, 40 ]) + PBrown(0, 1, -15, 10)
-amp = PSeq([ 45, 35, 35, 40 ]) + PBrown(0, 1, -15, 10)
+amp = PSeq([45, 35, 25, 40]) + PBrown(0, 1, -15, 10)
+amp = PSeq([45, 35, 35, 40]) + PBrown(0, 1, -15, 10)
gate = PBrown(1.5, 0.01, 0.6, 2.5)
timeline = Timeline(120)
-timeline.sched({ 'note': seq, 'amp': amp, 'dur': 0.25, 'gate': gate })
-timeline.sched({ 'note': seq.copy() + 24, 'amp': amp.copy(), 'dur': 0.5, 'gate': gate.copy() })
-timeline.sched({ 'note': seq.copy() - 24, 'amp': 10 + amp.copy() * 0.5, 'dur': PChoice([ 4, 4, 6, 8 ]), 'gate': gate.copy() })
+timeline.sched({'note': seq, 'amp': amp, 'dur': 0.25, 'gate': gate})
+timeline.sched({'note': seq.copy() + 24,
+ 'amp': amp.copy(),
+ 'dur': 0.5,
+ 'gate': gate.copy()})
+timeline.sched({'note': seq.copy() -
+ 24, 'amp': 10 +
+ amp.copy() *
+ 0.5, 'dur': PChoice([4, 4, 6, 8]), 'gate': gate.copy()})
timeline.run()
-
diff --git a/examples/ex-walk.py b/examples/ex-walk.py
index bd4fab1..cc9637d 100755
--- a/examples/ex-walk.py
+++ b/examples/ex-walk.py
@@ -5,15 +5,15 @@
#------------------------------------------------------------------------
# walk up and down a minor scale
#------------------------------------------------------------------------
-scale = Scale([ 0, 2, 3, 7, 9, 11 ])
-degree = PBrown(0, 2, -8, 16, repeats = False)
+scale = Scale([0, 2, 3, 7, 9, 11])
+degree = PBrown(0, 2, -8, 16, repeats=False)
notes = PDegree(degree, scale) + 60
#------------------------------------------------------------------------
# add a slight 4/4 emphasis and moderate variation in velocity
#------------------------------------------------------------------------
-amp = PSeq([ 40, 30, 20, 25 ]) + PBrown(0, 2, -10, 10)
+amp = PSeq([40, 30, 20, 25]) + PBrown(0, 2, -10, 10)
timeline = Timeline(170)
-timeline.sched({ 'note': notes, 'dur': 0.25, 'gate': 0.9, 'amp' : amp })
+timeline.sched({'note': notes, 'dur': 0.25, 'gate': 0.9, 'amp': amp})
timeline.run()
diff --git a/isobar/__init__.py b/isobar/__init__.py
index 139a0d0..0863498 100644
--- a/isobar/__init__.py
+++ b/isobar/__init__.py
@@ -1,17 +1,17 @@
__version__ = "0"
__author__ = "Daniel Jones "
-from isobar.pattern import *
-from isobar.note import *
-from isobar.scale import *
-from isobar.chord import *
-from isobar.key import *
-from isobar.util import *
-from isobar.timeline import *
+from pattern import *
+from note import *
+from scale import *
+from chord import *
+from key import *
+from util import *
+from timeline import *
import sys
-FOREVER = sys.maxint
+FOREVER = sys.maxsize
# REST = -sys.maxint - 1
# END = -sys.maxint + 1
diff --git a/isobar/chord.py b/isobar/chord.py
index 66e8ae9..a7d2075 100644
--- a/isobar/chord.py
+++ b/isobar/chord.py
@@ -1,57 +1,63 @@
import random
import copy
+
class Chord:
- """ Represents a chord made up of 1 or more note intervals.
- """
-
- dict = { }
-
- def __init__(self, intervals = [], root = 0, name = "unnamed chord"):
- self.intervals = intervals
- self.name = name
- self.root = root
- if not Chord.dict.has_key(name):
- Chord.dict[name] = self
-
- def __str__(self):
- return "%s %s%s" % (self.name, self.semitones(), (" + %d" % self.root) if self.root > 0 else "")
-
- @property
- def semitones(self):
- semitones = [0] + map(lambda n: sum(self.intervals[0:n+1]), range(len(self.intervals)))
- return semitones
-
- @staticmethod
- def byname(name):
- return Chord.dict[name]
-
- @staticmethod
- def random():
- key = random.choice(Chord.dict.keys())
- c = copy.deepcopy(Chord.dict[key])
- c.root = random.randint(0, 12)
- return c
-
- @staticmethod
- def arbitrary(name = "chord"):
- intervals_poss = [ 2, 3, 3, 4, 4, 5, 6 ]
- intervals = []
- top = random.randint(12, 18)
- n = 0
- while True:
- interval = random.choice(intervals_poss)
- n += interval
- if n > top:
- break
- intervals.append(interval)
-
- return Chord(intervals, 0, name if name else "chord", random.randint(0, 12))
-
-Chord.major = Chord([ 4, 3, 5 ], 0, "major")
-Chord.minor = Chord([ 3, 4, 5 ], 0, "minor")
-Chord.diminished = Chord([ 3, 3, 6 ], 0, "diminished")
-Chord.augmented = Chord([ 4, 4, 4 ], 0, "diminished")
-
-Chord.sus4 = Chord([ 5, 2, 5 ], 0, "sus4")
-Chord.sus2 = Chord([ 7, 2, 5 ], 0, "sus4")
+
+ """ Represents a chord made up of 1 or more note intervals.
+ """
+
+ dict = {}
+
+ def __init__(self, intervals=[], root=0, name="unnamed chord"):
+ self.intervals = intervals
+ self.name = name
+ self.root = root
+ if name not in Chord.dict:
+ Chord.dict[name] = self
+
+ def __str__(self):
+ return "%s %s%s" % (self.name,
+ self.semitones(),
+ (" + %d" % self.root) if self.root > 0 else "")
+
+ @property
+ def semitones(self):
+ semitones = [
+ 0] + map(lambda n: sum(self.intervals[0:n + 1]), range(len(self.intervals)))
+ return semitones
+
+ @staticmethod
+ def byname(name):
+ return Chord.dict[name]
+
+ @staticmethod
+ def random():
+ key = random.choice(Chord.dict.keys())
+ c = copy.deepcopy(Chord.dict[key])
+ c.root = random.randint(0, 12)
+ return c
+
+ @staticmethod
+ def arbitrary(name="chord"):
+ intervals_poss = [2, 3, 3, 4, 4, 5, 6]
+ intervals = []
+ top = random.randint(12, 18)
+ n = 0
+ while True:
+ interval = random.choice(intervals_poss)
+ n += interval
+ if n > top:
+ break
+ intervals.append(interval)
+
+ return Chord(
+ intervals, 0, name if name else "chord", random.randint(0, 12))
+
+Chord.major = Chord([4, 3, 5], 0, "major")
+Chord.minor = Chord([3, 4, 5], 0, "minor")
+Chord.diminished = Chord([3, 3, 6], 0, "diminished")
+Chord.augmented = Chord([4, 4, 4], 0, "diminished")
+
+Chord.sus4 = Chord([5, 2, 5], 0, "sus4")
+Chord.sus2 = Chord([7, 2, 5], 0, "sus4")
diff --git a/isobar/io/midi.py b/isobar/io/midi.py
index b4154c3..4f598cf 100644
--- a/isobar/io/midi.py
+++ b/isobar/io/midi.py
@@ -1,118 +1,124 @@
try:
- import rtmidi
+ import mido
except:
- print "rtmidi not found, no MIDI support available."
-
-import random
-import time
+ print "mido not found, no MIDI support available."
import isobar
-from isobar.note import *
MIDIIN_DEFAULT = "IAC Driver A"
MIDIOUT_DEFAULT = "IAC Driver A"
class MidiIn:
- def __init__(self, target = MIDIOUT_DEFAULT):
- self.midi = rtmidi.MidiIn()
-
- #------------------------------------------------------------------------
- # don't ignore MIDI clock messages (is on by default)
- #------------------------------------------------------------------------
- self.midi.ignore_types(timing = False)
- self.clocktarget = None
- ports = self.midi.get_ports()
- if len(ports) == 0:
- raise Exception, "No MIDI output ports found"
+ def __init__(self, target=MIDIOUT_DEFAULT):
+ self.midi = None
+ # don't ignore MIDI clock messages (is on by default)
+ # TODO: check
+ # self.midi.ignore_types(timing=False)
+ self.clocktarget = None
- for index, name in enumerate(ports):
- if name == target:
- isobar.log("Found MIDI input (%s)", name)
- self.midi.open_port(index)
+ ports = mido.get_input_names()
+ if len(ports) == 0:
+ raise Exception("No MIDI output ports found")
- if self.midi is None:
- isobar.log("Could not find MIDI source %s, using default", target)
- self.midi.open_port(0)
+ for name in ports:
+ if name == target:
+ isobar.log("Found MIDI input (%s)", name)
+ self.midi = mido.open_input(name)
- def callback(self, message, timestamp):
- message = message[0]
- data_type = message[0]
+ if self.midi is None:
+ isobar.log("Could not find MIDI source %s, using default", target)
+ self.midi = mido.open_input()
- if data_type == 248:
- if self.clocktarget is not None:
- self.clocktarget.tick()
+ def callback(self, message, timestamp):
+ message = message[0]
+ data_type = message[0]
- elif data_type == 250:
- # TODO: is this the right midi code?
- if self.clocktarget is not None:
- self.clocktarget.reset_to_beat()
+ if data_type == 248:
+ if self.clocktarget is not None:
+ self.clocktarget.tick()
- # print "%d %d (%d)" % (data_type, data_note, data_vel)
+ elif data_type == 250:
+ # TODO: is this the right midi code?
+ if self.clocktarget is not None:
+ self.clocktarget.reset_to_beat()
+ # print "%d %d (%d)" % (data_type, data_note, data_vel)
- def run(self):
- self.midi.set_callback(self.callback)
+ def run(self):
+ self.midi.set_callback(self.callback)
- def poll(self):
- """ used in markov-learner -- can we refactor? """
- message = self.midi.get_message()
- if not message:
- return
+ def poll(self):
+ """ used in markov-learner -- can we refactor? """
+ message = self.midi.get_message()
+ if not message:
+ return
- print message
- data_type, data_note, data_vel = message[0]
+ print message
+ data_type, data_note, data_vel = message[0]
- if (data_type & 0x90) > 0 and data_vel > 0:
- # note on
- return Note(data_note, data_vel)
+ if (data_type & 0x90) > 0 and data_vel > 0:
+ # note on
+ return isobar.Note(data_note, data_vel)
- def close(self):
- del self.midi
+ def close(self):
+ del self.midi
class MidiOut:
- def __init__(self, target = MIDIOUT_DEFAULT):
- self.midi = rtmidi.MidiOut()
-
- ports = self.midi.get_ports()
- if len(ports) == 0:
- raise Exception, "No MIDI output ports found"
-
- for index, name in enumerate(ports):
- if name == target:
- isobar.log("Found MIDI output (%s)" % name)
- self.midi.open_port(index)
-
- if self.midi is None:
- print "Could not find MIDI target %s, using default" % target
- self.midi.open_port(0)
-
- def tick(self, ticklen):
- pass
-
- def noteOn(self, note = 60, velocity = 64, channel = 0):
- if self.debug:
- print "[midi] channel %d, noteOn: (%d, %d)" % (channel, note, velocity)
- self.midi.send_message([ 0x90 + channel, int(note), int(velocity) ])
-
- def noteOff(self, note = 60, channel = 0):
- if self.debug:
- print "[midi] channel %d, noteOff: %d" % (channel, note)
- self.midi.send_message([ 0x80 + channel, int(note), 0 ])
-
- def allNotesOff(self, channel = 0):
- if self.debug:
- print "[midi] channel %d, allNotesOff"
- for n in range(128):
- self.noteOff(n, channel)
-
- def control(self, control = 0, value = 0, channel = 0):
- print "*** [CTRL] channel %d, control %d: %d" % (channel, control, value)
- if self.debug:
- print "[midi] channel %d, control %d: %d" % (channel, control, value)
- self.midi.send_message([ 0xB0 + channel, int(control), int(value) ])
-
- def __destroy__(self):
- del self.midi
+
+ def __init__(self, target=MIDIOUT_DEFAULT):
+ self.midi = None
+ self.debug = False
+ ports = mido.get_output_names()
+ if len(ports) == 0:
+ raise Exception("No MIDI output ports found")
+
+ for name in ports:
+ if name == target:
+ isobar.log("Found MIDI output (%s)" % name)
+ self.midi = mido.open_output(name)
+
+ if self.midi is None:
+ print "Could not find MIDI target %s, using default" % target
+ self.midi = mido.open_output()
+
+ def tick(self, ticklen):
+ pass
+
+ def noteOn(self, note=60, velocity=64, channel=0):
+ if self.debug:
+ print "[midi] channel %d, noteOn: (%d, %d)" % (
+ channel, note, velocity
+ )
+ msg = mido.Message(
+ 'note_on', channel=channel, note=note, velocity=velocity
+ )
+ self.midi.send(msg)
+
+ def noteOff(self, note=60, channel=0):
+ if self.debug:
+ print "[midi] channel %d, noteOff: %d" % (channel, note)
+ msg = mido.Message(
+ 'note_off', channel=channel, note=note
+ )
+ self.midi.send(msg)
+
+ def allNotesOff(self, channel=0):
+ if self.debug:
+ print "[midi] channel %d, allNotesOff"
+ for n in range(128):
+ self.noteOff(n, channel)
+
+ def control(self, control=0, value=0, channel=0):
+ print "*** [CTRL] channel %d, control %d: %d" % (channel, control, value)
+ if self.debug:
+ print "[midi] channel %d, control %d: %d" % (channel, control, value)
+ msg = mido.Message(
+ 'control_change', channel=channel, control=control, value=value
+ )
+ self.midi.send(msg)
+
+ def __destroy__(self):
+ del self.midi
diff --git a/isobar/io/midifile.py b/isobar/io/midifile.py
index 28219fb..511012e 100644
--- a/isobar/io/midifile.py
+++ b/isobar/io/midifile.py
@@ -1,4 +1,4 @@
-from __future__ import absolute_import
+from __future__ import absolute_import
from isobar.note import *
from isobar.pattern.core import *
@@ -6,194 +6,220 @@
import midi
+
class MidiFileIn:
- def __init__(self):
- pass
-
- def read(self, filename, quantize = 0.25):
- reader = midi.FileReader()
- data = reader.read(file(filename))
-
- class Note:
- def __init__(self, pitch, velocity, location, duration = None):
- # pitch = MIDI 0..127
- self.pitch = pitch
- # velocity = MIDI 0..127
- self.velocity = velocity
- # location in time, beats
- self.location = location
- # duration in time, beats
- self.duration = duration
-
- notes = []
- for track in data:
- track.make_ticks_abs()
- for event in filter(lambda event: isinstance(event, midi.events.NoteEvent), track):
- location = event.tick / 96.0
- if isinstance(event, midi.events.NoteOnEvent) and event.velocity > 0:
- print "(%.2f beats) %s. \t(note = %d, velocity = %d)" % (location, miditoname(event.pitch), event.pitch, event.velocity)
-
- #------------------------------------------------------------------------
- # anomaly: sometimes a midi file might have a note on when the previous
- # note has not finished; end it automatically
- #------------------------------------------------------------------------
- for note in reversed(notes):
- if note.pitch == event.pitch:
- if not note.duration:
- print "note-on found without note-off; cancelling previous note"
- duration = location - note.location
- note.duration = duration
- if duration == 0:
- print "*** DURATION OF ZERO??"
- break
-
- note = Note(event.pitch, event.velocity, location)
- notes.append(note)
-
- #------------------------------------------------------------------------
- # A NoteOn event with velocity == 0 also acts as a NoteOff.
- #------------------------------------------------------------------------
- if isinstance(event, midi.events.NoteOffEvent) or (isinstance(event, midi.events.NoteOnEvent) and event.velocity == 0):
- print "(%.2f beats) %s off \t(note = %d, velocity = %d)" % (location, miditoname(event.pitch), event.pitch, event.velocity)
- found = False
- for note in reversed(notes):
- if note.pitch == event.pitch:
- duration = location - note.location
- # print " -> duration = %.2f beats" % duration
- note.duration = duration
- found = True
- break
- if not found:
- print "*** NOTE-OFF FOUND WITHOUT NOTE-ON ***"
-
- #------------------------------------------------------------------------
- # Construct a sequence which honours chords and relative lengths.
- # First, group all notes by their starting time.
- #------------------------------------------------------------------------
- notes_by_time = {}
- for note in notes:
- print "(%.2f) %d/%d, %s" % (note.location, note.pitch, note.velocity, note.duration)
- location = note.location
- if quantize is not None:
- location = round(location / quantize) * quantize
- if location in notes_by_time:
- notes_by_time[location].append(note)
- else:
- notes_by_time[location] = [ note ]
-
- note_dict = {
- "note" : [],
- "amp" : [],
- "gate" : [],
- "dur" : []
- }
-
- #------------------------------------------------------------------------
- # Next, iterate through groups of notes chronologically, figuring out
- # appropriate parameters for duration (eg, inter-note distance) and
- # gate (eg, proportion of distance note extends across).
- #------------------------------------------------------------------------
- times = sorted(notes_by_time.keys())
- for i in range(len(times)):
- time = times[i]
- notes = notes_by_time[time]
-
- #------------------------------------------------------------------------
- # Our duration is always determined by the time of the next note event.
- # If a next note does not exist, this is the last note of the sequence;
- # use the maximal length of note currently playing (assuming a chord)
- #------------------------------------------------------------------------
- if i < len(times) - 1:
- next_time = times[i + 1]
- else:
- next_time = time + max([ note.duration for note in notes ])
-
- dur = next_time - time
- note_dict["dur"].append(dur)
-
- if len(notes) > 1:
- note_dict["note"].append(tuple(note.pitch for note in notes))
- note_dict["amp"].append(tuple(note.velocity for note in notes))
- note_dict["gate"].append(tuple(note.duration / dur for note in notes))
- else:
- note = notes[0]
- note_dict["note"].append(note.pitch)
- note_dict["amp"].append(note.velocity)
- note_dict["gate"].append(note.duration / dur)
-
- return note_dict
+
+ def __init__(self):
+ pass
+
+ def read(self, filename, quantize=0.25):
+ reader = midi.FileReader()
+ data = reader.read(file(filename))
+
+ class Note:
+
+ def __init__(self, pitch, velocity, location, duration=None):
+ # pitch = MIDI 0..127
+ self.pitch = pitch
+ # velocity = MIDI 0..127
+ self.velocity = velocity
+ # location in time, beats
+ self.location = location
+ # duration in time, beats
+ self.duration = duration
+
+ notes = []
+ for track in data:
+ track.make_ticks_abs()
+ for event in filter(
+ lambda event: isinstance(
+ event,
+ midi.events.NoteEvent),
+ track):
+ location = event.tick / 96.0
+ if isinstance(
+ event,
+ midi.events.NoteOnEvent) and event.velocity > 0:
+ print "(%.2f beats) %s. \t(note = %d, velocity = %d)" % (location, miditoname(event.pitch), event.pitch, event.velocity)
+
+ #----------------------------------------------------------
+ # anomaly: sometimes a midi file might have a note on when the previous
+ # note has not finished; end it automatically
+ #----------------------------------------------------------
+ for note in reversed(notes):
+ if note.pitch == event.pitch:
+ if not note.duration:
+ print "note-on found without note-off; cancelling previous note"
+ duration = location - note.location
+ note.duration = duration
+ if duration == 0:
+ print "*** DURATION OF ZERO??"
+ break
+
+ note = Note(event.pitch, event.velocity, location)
+ notes.append(note)
+
+ #--------------------------------------------------------------
+ # A NoteOn event with velocity == 0 also acts as a NoteOff.
+ #--------------------------------------------------------------
+ if isinstance(
+ event,
+ midi.events.NoteOffEvent) or (
+ isinstance(
+ event,
+ midi.events.NoteOnEvent) and event.velocity == 0):
+ print "(%.2f beats) %s off \t(note = %d, velocity = %d)" % (location, miditoname(event.pitch), event.pitch, event.velocity)
+ found = False
+ for note in reversed(notes):
+ if note.pitch == event.pitch:
+ duration = location - note.location
+ # print " -> duration = %.2f beats" % duration
+ note.duration = duration
+ found = True
+ break
+ if not found:
+ print "*** NOTE-OFF FOUND WITHOUT NOTE-ON ***"
+
+ #----------------------------------------------------------------------
+ # Construct a sequence which honours chords and relative lengths.
+ # First, group all notes by their starting time.
+ #----------------------------------------------------------------------
+ notes_by_time = {}
+ for note in notes:
+ print "(%.2f) %d/%d, %s" % (note.location, note.pitch, note.velocity, note.duration)
+ location = note.location
+ if quantize is not None:
+ location = round(location / quantize) * quantize
+ if location in notes_by_time:
+ notes_by_time[location].append(note)
+ else:
+ notes_by_time[location] = [note]
+
+ note_dict = {
+ "note": [],
+ "amp": [],
+ "gate": [],
+ "dur": []
+ }
+
+ #----------------------------------------------------------------------
+ # Next, iterate through groups of notes chronologically, figuring out
+ # appropriate parameters for duration (eg, inter-note distance) and
+ # gate (eg, proportion of distance note extends across).
+ #----------------------------------------------------------------------
+ times = sorted(notes_by_time.keys())
+ for i in range(len(times)):
+ time = times[i]
+ notes = notes_by_time[time]
+
+ #------------------------------------------------------------------
+ # Our duration is always determined by the time of the next note event.
+ # If a next note does not exist, this is the last note of the sequence;
+ # use the maximal length of note currently playing (assuming a chord)
+ #------------------------------------------------------------------
+ if i < len(times) - 1:
+ next_time = times[i + 1]
+ else:
+ next_time = time + max([note.duration for note in notes])
+
+ dur = next_time - time
+ note_dict["dur"].append(dur)
+
+ if len(notes) > 1:
+ note_dict["note"].append(tuple(note.pitch for note in notes))
+ note_dict["amp"].append(tuple(note.velocity for note in notes))
+ note_dict["gate"].append(
+ tuple(
+ note.duration /
+ dur for note in notes))
+ else:
+ note = notes[0]
+ note_dict["note"].append(note.pitch)
+ note_dict["amp"].append(note.velocity)
+ note_dict["gate"].append(note.duration / dur)
+
+ return note_dict
+
class MidiFileOut:
- """ Write events to a MIDI file.
- Requires the MIDIUtil package:
- https://code.google.com/p/midiutil/ """
-
- def __init__(self, numtracks = 16):
- from midiutil.MidiFile import MIDIFile
-
- self.score = MIDIFile(numtracks)
- self.track = 0
- self.channel = 0
- self.volume = 64
- self.time = 0
-
- def tick(self, ticklen):
- self.time += ticklen
-
- def noteOn(self, note = 60, velocity = 64, channel = 0, duration = 1):
- #------------------------------------------------------------------------
- # avoid rounding errors
- #------------------------------------------------------------------------
- time = round(self.time, 5)
- self.score.addNote(channel, channel, note, time, duration, velocity)
-
- def noteOff(self, note = 60, channel = 0):
- time = round(self.time, 5)
- self.score.addNote(channel, channel, note, time, duration, 0)
-
- def writeFile(self, filename = "score.mid"):
- fd = open(filename, 'wb')
- self.score.writeFile(fd)
- fd.close()
+
+ """ Write events to a MIDI file.
+ Requires the MIDIUtil package:
+ https://code.google.com/p/midiutil/ """
+
+ def __init__(self, numtracks=16):
+ from midiutil.MidiFile import MIDIFile
+
+ self.score = MIDIFile(numtracks)
+ self.track = 0
+ self.channel = 0
+ self.volume = 64
+ self.time = 0
+
+ def tick(self, ticklen):
+ self.time += ticklen
+
+ def noteOn(self, note=60, velocity=64, channel=0, duration=1):
+ #----------------------------------------------------------------------
+ # avoid rounding errors
+ #----------------------------------------------------------------------
+ time = round(self.time, 5)
+ self.score.addNote(channel, channel, note, time, duration, velocity)
+
+ def noteOff(self, note=60, channel=0):
+ time = round(self.time, 5)
+ self.score.addNote(channel, channel, note, time, duration, 0)
+
+ def writeFile(self, filename="score.mid"):
+ fd = open(filename, 'wb')
+ self.score.writeFile(fd)
+ fd.close()
+
class PatternWriterMIDI:
- """ Writes a pattern to a MIDI file.
- Requires the MIDIUtil package:
- https://code.google.com/p/midiutil/ """
-
- def __init__(self, numtracks = 1):
- from midiutil.MidiFile import MIDIFile
-
- self.score = MIDIFile(numtracks)
- self.track = 0
- self.channel = 0
- self.volume = 64
-
- def addTrack(self, pattern, tracknumber = 0, trackname = "track", dur = 1.0):
- time = 0
- # naive approach: assume every duration is 1
- # TODO: accept dicts or PDicts
- try:
- for note in pattern:
- vdur = Pattern.value(dur)
- if note is not None and vdur is not None:
- self.score.addNote(tracknumber, self.channel, note, time, vdur, self.volume)
- time += vdur
- else:
- time += vdur
- except StopIteration:
- # a StopIteration exception means that an input pattern has been exhausted.
- # catch it and treat the track as completed.
- pass
-
- def addTimeline(self, timeline):
- # TODO: translate entire timeline into MIDI
- # difficulties: need to handle degree/transpose params
- # need to handle channels properly, and reset numtracks
- pass
-
- def writeFile(self, filename = "score.mid"):
- fd = open(filename, 'wb')
- self.score.writeFile(fd)
- fd.close()
+ """ Writes a pattern to a MIDI file.
+ Requires the MIDIUtil package:
+ https://code.google.com/p/midiutil/ """
+
+ def __init__(self, numtracks=1):
+ from midiutil.MidiFile import MIDIFile
+
+ self.score = MIDIFile(numtracks)
+ self.track = 0
+ self.channel = 0
+ self.volume = 64
+
+ def addTrack(self, pattern, tracknumber=0, trackname="track", dur=1.0):
+ time = 0
+ # naive approach: assume every duration is 1
+ # TODO: accept dicts or PDicts
+ try:
+ for note in pattern:
+ vdur = Pattern.value(dur)
+ if note is not None and vdur is not None:
+ self.score.addNote(
+ tracknumber,
+ self.channel,
+ note,
+ time,
+ vdur,
+ self.volume)
+ time += vdur
+ else:
+ time += vdur
+ except StopIteration:
+ # a StopIteration exception means that an input pattern has been exhausted.
+ # catch it and treat the track as completed.
+ pass
+
+ def addTimeline(self, timeline):
+ # TODO: translate entire timeline into MIDI
+ # difficulties: need to handle degree/transpose params
+ # need to handle channels properly, and reset numtracks
+ pass
+
+ def writeFile(self, filename="score.mid"):
+ fd = open(filename, 'wb')
+ self.score.writeFile(fd)
+ fd.close()
diff --git a/isobar/io/osc.py b/isobar/io/osc.py
index 123e8cd..5b5fc61 100644
--- a/isobar/io/osc.py
+++ b/isobar/io/osc.py
@@ -8,43 +8,44 @@
MIDIIN_DEFAULT = "IAC Driver A"
MIDIOUT_DEFAULT = "IAC Driver A"
+
class OSCOut:
- """ OSCOut: Wraps MIDI messages in OSC.
- /note [ note, velocity, channel ]
- /control [ control, value, channel ] """
- def __init__(self, host = "localhost", port = 7000):
- self.osc = OSCClient()
- self.osc.connect((host, port))
+ """ OSCOut: Wraps MIDI messages in OSC.
+ /note [ note, velocity, channel ]
+ /control [ control, value, channel ] """
- def tick(self, ticklen):
- pass
+ def __init__(self, host="localhost", port=7000):
+ self.osc = OSCClient()
+ self.osc.connect((host, port))
- def noteOn(self, note = 60, velocity = 64, channel = 0):
- msg = OSCMessage("/note")
- msg.extend([ note, velocity, channel ])
- self.osc.send(msg)
+ def tick(self, ticklen):
+ pass
- def noteOff(self, note = 60, channel = 0):
- msg = OSCMessage("/note")
- msg.extend([ note, 0, channel ])
- self.osc.send(msg)
+ def noteOn(self, note=60, velocity=64, channel=0):
+ msg = OSCMessage("/note")
+ msg.extend([note, velocity, channel])
+ self.osc.send(msg)
- def allNotesOff(self, channel = 0):
- for n in range(128):
- self.noteOff(n, channel)
+ def noteOff(self, note=60, channel=0):
+ msg = OSCMessage("/note")
+ msg.extend([note, 0, channel])
+ self.osc.send(msg)
- def control(self, control, value, channel = 0):
- msg = OSCMessage("/control")
- msg.extend([ control, value, channel ])
- self.osc.send(msg)
+ def allNotesOff(self, channel=0):
+ for n in range(128):
+ self.noteOff(n, channel)
- def __destroy__(self):
- self.osc.close()
+ def control(self, control, value, channel=0):
+ msg = OSCMessage("/control")
+ msg.extend([control, value, channel])
+ self.osc.send(msg)
- def send(self, address, params = None):
- msg = OSCMessage(address)
- msg.extend(params)
- print "osc: %s (%s)" % (address, params)
- self.osc.send(msg)
+ def __destroy__(self):
+ self.osc.close()
+ def send(self, address, params=None):
+ msg = OSCMessage(address)
+ msg.extend(params)
+ print "osc: %s (%s)" % (address, params)
+ self.osc.send(msg)
diff --git a/isobar/io/socketio.py b/isobar/io/socketio.py
index ef65a60..227981a 100644
--- a/isobar/io/socketio.py
+++ b/isobar/io/socketio.py
@@ -4,39 +4,40 @@
from isobar.note import *
+
class SocketIOOut:
- """ SocketIOOut: Support for sending note on/off events via websockets.
- Two types of event are sent at the moment:
- note [ index, velocity, channel ] : The MIDI note number depressed.
- For note-off, velocity is zero.
- control [ index, value, channel ] : A MIDI control value
- """
+ """ SocketIOOut: Support for sending note on/off events via websockets.
+ Two types of event are sent at the moment:
- def __init__(self, host = "localhost", port = 9000):
- self.socket = socketIO_client.SocketIO(host, port)
+ note [ index, velocity, channel ] : The MIDI note number depressed.
+ For note-off, velocity is zero.
+ control [ index, value, channel ] : A MIDI control value
+ """
- def tick(self, ticklen):
- pass
+ def __init__(self, host="localhost", port=9000):
+ self.socket = socketIO_client.SocketIO(host, port)
- def event(self, event):
- # import pprint
- # pprint.pprint(event)
- self.socket.emit("event", event)
+ def tick(self, ticklen):
+ pass
- def noteOn(self, note = 60, velocity = 64, channel = 0):
- self.socket.emit("note", note, velocity, channel)
+ def event(self, event):
+ # import pprint
+ # pprint.pprint(event)
+ self.socket.emit("event", event)
- def noteOff(self, note = 60, channel = 0):
- self.socket.emit("note", note, 0, channel)
+ def noteOn(self, note=60, velocity=64, channel=0):
+ self.socket.emit("note", note, velocity, channel)
- def allNotesOff(self, channel = 0):
- for n in range(128):
- self.noteOff(n, channel)
+ def noteOff(self, note=60, channel=0):
+ self.socket.emit("note", note, 0, channel)
- def control(self, control, value, channel = 0):
- self.socket.emit("control", control, value, channel)
+ def allNotesOff(self, channel=0):
+ for n in range(128):
+ self.noteOff(n, channel)
- def __destroy__(self):
- self.socket.close()
+ def control(self, control, value, channel=0):
+ self.socket.emit("control", control, value, channel)
+ def __destroy__(self):
+ self.socket.close()
diff --git a/isobar/key.py b/isobar/key.py
index 9ca26b6..1b34156 100644
--- a/isobar/key.py
+++ b/isobar/key.py
@@ -4,119 +4,122 @@
import random
+
class Key:
- """ Represents a harmonic structure, containing a tonic and scale.
- """
-
- def __init__(self, tonic = 0, scale = Scale.major):
- if type(tonic) == str:
- tonic = nametomidi(tonic)
- if type(scale) == str:
- scale = Scale.byname(scale)
-
- self.tonic = tonic
- self.scale = scale
-
- def __eq__(self, other):
- return self.tonic == other.tonic and self.scale == other.scale
-
- def __str__(self):
- return "key: %s %s" % (miditopitch(self.tonic), self.scale.name)
-
- def __repr__(self):
- return 'Key(%s, "%s")' % (self.tonic, self.scale.name)
-
- def get(self, degree):
- """ Returns the th semitone within this key. """
- if degree is None:
- return None
-
- semitone = self.scale[degree]
- return semitone + self.tonic
-
- def __getitem__(self, degree):
- return self.get(degree)
-
- def __contains__(self, semitone):
- return (semitone % self.scale.octave_size) in self.semitones
-
- @property
- def semitones(self):
- semitones = map(lambda n: (n + self.tonic) % self.scale.octave_size, self.scale.semitones)
- semitones.sort()
- return semitones
-
- def nearest_note(self, note):
- if note in self:
- return note
- else:
- octave, pitch = divmod(note, self.scale.octave_size)
- nearest_semi = None
- nearest_dist = None
- for semi in self.semitones:
- dist = abs(semi - pitch)
- if nearest_dist is None or dist < nearest_dist:
- nearest_semi = semi
- nearest_dist = dist
- return (octave * self.scale.octave_size) + nearest_semi
-
- def voiceleading(self, other):
- """ Returns the most parsimonious voice leading between this key
- and , as a list of N tuples (semiA, semiB) where N is the
- maximal length of (this, other), and semiA and semiB are members
- of each. May not be bijective. """
-
- if len(self.semitones) > len(other.semitones):
- semisA = self.semitones
- semisB = other.semitones
- else:
- semisA = other.semitones
- semisB = self.semitones
- semisB = list(reversed(semisB))
-
- leading = []
- for semiA in semisA:
- distances = []
- for semiB in semisB:
- distance = abs(semiA - semiB)
- if distance > self.scale.octave_size / 2:
- distance = self.scale.octave_size - distance
- distances.append(distance)
- index = distances.index(min(distances))
- leading.append((semiA, semisB[index]))
-
- return leading
-
- def distance(self, other):
- leading = self.voiceleading(other)
- distance = sum(map(lambda (a, b): abs(a - b), leading))
- return distance
-
- def fadeto(self, other, level):
- """ level between 0..1 """
- semitones_a = self.semitones()
- semitones_b = other.semitones()
- semitones_shared = filter(lambda n: n in semitones_a, semitones_b)
- semitones_a_only = filter(lambda n: n not in semitones_b, semitones_a)
- semitones_b_only = filter(lambda n: n not in semitones_a, semitones_b)
-
- if level < 0.5:
- # scale from 1..0
- level = 1.0 - (level * 2)
- count_from_a = int(round(level * len(semitones_a_only)))
- return semitones_shared + semitones_a_only[0:count_from_a]
- else:
- # scale from 0..1
- level = 2 * (level - 0.5)
- count_from_b = int(round(level * len(semitones_b_only)))
- return semitones_shared + semitones_b_only[0:count_from_b]
-
- @staticmethod
- def random():
- t = random.randint(0, 11)
- s = Scale.random()
- return Key(t, s)
-
- @staticmethod
- def all():
- return [ Key(note, scale) for note in Note.all() for scale in Scale.all() ]
+
+ """ Represents a harmonic structure, containing a tonic and scale.
+ """
+
+ def __init__(self, tonic=0, scale=Scale.major):
+ if isinstance(tonic, str):
+ tonic = nametomidi(tonic)
+ if isinstance(scale, str):
+ scale = Scale.byname(scale)
+
+ self.tonic = tonic
+ self.scale = scale
+
+ def __eq__(self, other):
+ return self.tonic == other.tonic and self.scale == other.scale
+
+ def __str__(self):
+ return "key: %s %s" % (miditopitch(self.tonic), self.scale.name)
+
+ def __repr__(self):
+ return 'Key(%s, "%s")' % (self.tonic, self.scale.name)
+
+ def get(self, degree):
+ """ Returns the th semitone within this key. """
+ if degree is None:
+ return None
+
+ semitone = self.scale[degree]
+ return semitone + self.tonic
+
+ def __getitem__(self, degree):
+ return self.get(degree)
+
+ def __contains__(self, semitone):
+ return (semitone % self.scale.octave_size) in self.semitones
+
+ @property
+ def semitones(self):
+ semitones = sorted(map(lambda n: (n + self.tonic) %
+ self.scale.octave_size, self.scale.semitones))
+ return semitones
+
+ def nearest_note(self, note):
+ if note in self:
+ return note
+ else:
+ octave, pitch = divmod(note, self.scale.octave_size)
+ nearest_semi = None
+ nearest_dist = None
+ for semi in self.semitones:
+ dist = abs(semi - pitch)
+ if nearest_dist is None or dist < nearest_dist:
+ nearest_semi = semi
+ nearest_dist = dist
+ return (octave * self.scale.octave_size) + nearest_semi
+
+ def voiceleading(self, other):
+ """ Returns the most parsimonious voice leading between this key
+ and , as a list of N tuples (semiA, semiB) where N is the
+ maximal length of (this, other), and semiA and semiB are members
+ of each. May not be bijective. """
+
+ if len(self.semitones) > len(other.semitones):
+ semisA = self.semitones
+ semisB = other.semitones
+ else:
+ semisA = other.semitones
+ semisB = self.semitones
+ semisB = list(reversed(semisB))
+
+ leading = []
+ for semiA in semisA:
+ distances = []
+ for semiB in semisB:
+ distance = abs(semiA - semiB)
+ if distance > self.scale.octave_size / 2:
+ distance = self.scale.octave_size - distance
+ distances.append(distance)
+ index = distances.index(min(distances))
+ leading.append((semiA, semisB[index]))
+
+ return leading
+
+ def distance(self, other):
+ leading = self.voiceleading(other)
+ distance = sum(map(lambda a_b: abs(a_b[0] - a_b[1]), leading))
+ return distance
+
+ def fadeto(self, other, level):
+ """ level between 0..1 """
+ semitones_a = self.semitones()
+ semitones_b = other.semitones()
+ semitones_shared = filter(lambda n: n in semitones_a, semitones_b)
+ semitones_a_only = filter(lambda n: n not in semitones_b, semitones_a)
+ semitones_b_only = filter(lambda n: n not in semitones_a, semitones_b)
+
+ if level < 0.5:
+ # scale from 1..0
+ level = 1.0 - (level * 2)
+ count_from_a = int(round(level * len(semitones_a_only)))
+ return semitones_shared + semitones_a_only[0:count_from_a]
+ else:
+ # scale from 0..1
+ level = 2 * (level - 0.5)
+ count_from_b = int(round(level * len(semitones_b_only)))
+ return semitones_shared + semitones_b_only[0:count_from_b]
+
+ @staticmethod
+ def random():
+ t = random.randint(0, 11)
+ s = Scale.random()
+ return Key(t, s)
+
+ @staticmethod
+ def all():
+ return [Key(note, scale) for note in Note.all()
+ for scale in Scale.all()]
diff --git a/isobar/note.py b/isobar/note.py
index d612569..c99137b 100644
--- a/isobar/note.py
+++ b/isobar/note.py
@@ -1,24 +1,23 @@
-import math
-
from isobar.util import *
+
class Note(object):
- names = [ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" ]
+ names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
- def __init__(self, midinote = 60, velocity = 64, length = 1):
- self.midinote = midinote
- self.velocity = velocity
- self.length = length
+ def __init__(self, midinote=60, velocity=64, length=1):
+ self.midinote = midinote
+ self.velocity = velocity
+ self.length = length
- def __str__(self):
- if self.velocity == 0:
- return "rest"
+ def __str__(self):
+ if self.velocity == 0:
+ return "rest"
- return miditoname(self.midinote)
+ return miditoname(self.midinote)
- @staticmethod
- def all():
- return Note.names
+ @staticmethod
+ def all():
+ return Note.names
Note.rest = Note(0, 0, 0)
diff --git a/isobar/pattern/automate.py b/isobar/pattern/automate.py
index d2f46ad..19667ea 100644
--- a/isobar/pattern/automate.py
+++ b/isobar/pattern/automate.py
@@ -2,25 +2,28 @@
import math
+
class PAutomate(Pattern):
- pass
+ pass
+
class PASine(PAutomate):
- def __init__(self, length = 1, amp = 0.5):
- self.length = length
- self.amp = amp
- self.pos = 0.0
-
- def tick(self, ticklen):
- pos_norm = self.pos / self.length
- rv = math.sin(2.0 * math.pi * pos_norm) * self.amp
- #------------------------------------------------------------------------
- # normalize to [0, 1]
- #------------------------------------------------------------------------
- rv = 0.5 * rv + 0.5
-
- self.pos += ticklen
- if self.pos > self.length:
- self.pos -= self.length
-
- return rv
+
+ def __init__(self, length=1, amp=0.5):
+ self.length = length
+ self.amp = amp
+ self.pos = 0.0
+
+ def tick(self, ticklen):
+ pos_norm = self.pos / self.length
+ rv = math.sin(2.0 * math.pi * pos_norm) * self.amp
+ #----------------------------------------------------------------------
+ # normalize to [0, 1]
+ #----------------------------------------------------------------------
+ rv = 0.5 * rv + 0.5
+
+ self.pos += ticklen
+ if self.pos > self.length:
+ self.pos -= self.length
+
+ return rv
diff --git a/isobar/pattern/chance.py b/isobar/pattern/chance.py
index 15d7a82..1dad2e5 100644
--- a/isobar/pattern/chance.py
+++ b/isobar/pattern/chance.py
@@ -5,318 +5,355 @@
from isobar.pattern.core import *
from isobar.util import *
+
class PWhite(Pattern):
- """ PWhite: White noise between and .
- If values are given as floats, output values are also floats < max.
- If values are ints, output values are ints <= max (as random.randint)
-
- >>> PWhite(0, 10).nextn(16)
- [8, 10, 8, 1, 7, 3, 1, 9, 9, 3, 2, 10, 7, 5, 10, 4]
-
- >>> PWhite(0.0, 10.0).nextn(16)
- [3.6747936220022082, 0.61313530428271923, 9.1515368696591555, ... 6.2963694390145974 ]
- """
- def __init__(self, min, max = None):
- self.min = min
- self.max = max
-
- # also support a 1-argument case: PWhite(max)
- if self.max is None:
- self.max = min
- self.min = -self.max
-
- def next(self):
- min = self.value(self.min)
- max = self.value(self.max)
-
- if type(min) == float:
- return random.uniform(min, max)
- else:
- return random.randint(min, max)
+
+ """ PWhite: White noise between and .
+ If values are given as floats, output values are also floats < max.
+ If values are ints, output values are ints <= max (as random.randint)
+
+ >>> PWhite(0, 10).nextn(16)
+ [8, 10, 8, 1, 7, 3, 1, 9, 9, 3, 2, 10, 7, 5, 10, 4]
+
+ >>> PWhite(0.0, 10.0).nextn(16)
+ [3.6747936220022082, 0.61313530428271923, 9.1515368696591555, ... 6.2963694390145974 ]
+ """
+
+ def __init__(self, min, max=None):
+ self.min = min
+ self.max = max
+
+ # also support a 1-argument case: PWhite(max)
+ if self.max is None:
+ self.max = min
+ self.min = -self.max
+
+ def next(self):
+ min = self.value(self.min)
+ max = self.value(self.max)
+
+ if isinstance(min, float):
+ return random.uniform(min, max)
+ else:
+ return random.randint(min, max)
+
class PBrown(Pattern):
- """ PBrown: Brownian noise, beginning at , step +/-.
- Set to False to prevent consecutive repeats.
- """
- def __init__(self, value = 0, step = 0.1, min = -sys.maxint, max = sys.maxint, repeats = True, length = sys.maxint):
- self.init = value
- self.value = value
- self.step = step
- self.min = min
- self.max = max
- self.length = length
- self.repeats = repeats
- self.pos = 0
-
- def reset(self):
- self.value = self.init
- self.pos = 0
-
- Pattern.reset(self)
-
- def next(self):
- # pull out modulatable values
- vstep = Pattern.value(self.step)
- vmin = Pattern.value(self.min)
- vmax = Pattern.value(self.max)
- vrepeats = Pattern.value(self.repeats)
-
- if self.pos >= self.length:
- raise StopIteration
- rv = self.value
- self.pos += 1
- if type(vstep) == float:
- self.value += random.uniform(-vstep, vstep)
- else:
- # select new offset without repeats
- steps = range(-vstep, vstep + 1)
- if not vrepeats:
- steps.remove(0)
- self.value += random.choice(steps)
- self.value = min(max(self.value, vmin), vmax)
-
- return rv
+
+ """ PBrown: Brownian noise, beginning at , step +/-.
+ Set to False to prevent consecutive repeats.
+ """
+
+ def __init__(
+ self,
+ value=0,
+ step=0.1,
+ min=-sys.maxsize,
+ max=sys.maxsize,
+ repeats=True,
+ length=sys.maxsize):
+ self.init = value
+ self.value = value
+ self.step = step
+ self.min = min
+ self.max = max
+ self.length = length
+ self.repeats = repeats
+ self.pos = 0
+
+ def reset(self):
+ self.value = self.init
+ self.pos = 0
+
+ Pattern.reset(self)
+
+ def next(self):
+ # pull out modulatable values
+ vstep = Pattern.value(self.step)
+ vmin = Pattern.value(self.min)
+ vmax = Pattern.value(self.max)
+ vrepeats = Pattern.value(self.repeats)
+
+ if self.pos >= self.length:
+ raise StopIteration
+ rv = self.value
+ self.pos += 1
+ if isinstance(vstep, float):
+ self.value += random.uniform(-vstep, vstep)
+ else:
+ # select new offset without repeats
+ steps = range(-vstep, vstep + 1)
+ if not vrepeats:
+ steps.remove(0)
+ self.value += random.choice(steps)
+ self.value = min(max(self.value, vmin), vmax)
+
+ return rv
class PWalk(Pattern):
- """ PWalk: Random walk around list.
- Jumps between and steps inclusive.
- >>> PWalk([ 0, 2, 5, 8, 11 ], min = 1, max = 2).nextn(16)
- [8, 11, 0, 8, 0, 11, 2, 11, 2, 0, 5, 8, 11, 8, 5, 8]
- """
- def __init__(self, values = [], min = 1, max = 1):
- self.values = values
- self.min = min
- self.max = max
- self.pos = 0
+ """ PWalk: Random walk around list.
+ Jumps between and steps inclusive.
+
+ >>> PWalk([ 0, 2, 5, 8, 11 ], min = 1, max = 2).nextn(16)
+ [8, 11, 0, 8, 0, 11, 2, 11, 2, 0, 5, 8, 11, 8, 5, 8]
+ """
+
+ def __init__(self, values=[], min=1, max=1):
+ self.values = values
+ self.min = min
+ self.max = max
+ self.pos = 0
- def next(self):
- vvalues = Pattern.value(self.values)
- vmin = Pattern.value(self.min)
- vmax = Pattern.value(self.max)
+ def next(self):
+ vvalues = Pattern.value(self.values)
+ vmin = Pattern.value(self.min)
+ vmax = Pattern.value(self.max)
- move = random.randint(vmin, vmax)
- move = 0 - move if random.uniform(0, 1) < 0.5 else move
- self.pos += move
+ move = random.randint(vmin, vmax)
+ move = 0 - move if random.uniform(0, 1) < 0.5 else move
+ self.pos += move
- while self.pos < 0:
- self.pos += len(vvalues)
- while self.pos >= len(vvalues):
- self.pos -= len(vvalues)
+ while self.pos < 0:
+ self.pos += len(vvalues)
+ while self.pos >= len(vvalues):
+ self.pos -= len(vvalues)
- return vvalues[self.pos]
+ return vvalues[self.pos]
class PChoice(Pattern):
- """ PChoice: Random selection from
- >>> p = PChoice([ 0, 1, 10, 11 ])
- >>> p.nextn(16)
- [11, 1, 0, 10, 1, 11, 1, 0, 11, 1, 11, 1, 1, 11, 11, 1]
- """
- def __init__(self, values = []):
- self.values = values
+ """ PChoice: Random selection from
+
+ >>> p = PChoice([ 0, 1, 10, 11 ])
+ >>> p.nextn(16)
+ [11, 1, 0, 10, 1, 11, 1, 0, 11, 1, 11, 1, 1, 11, 11, 1]
+ """
+
+ def __init__(self, values=[]):
+ self.values = values
+
+ def next(self):
+ return self.values[random.randint(0, len(self.values) - 1)]
- def next(self):
- return self.values[random.randint(0, len(self.values) - 1)]
class PWChoice(Pattern):
- """ PWChoice: Random selection from , weighted by .
- and must be the same length, but not
- necessarily normalised.
- >>> p = PWChoice([ 1, 11, 111 ], [ 8, 2, 1 ])
- >>> p.nextn(16)
- [111, 1, 1, 111, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1]
- """
- def __init__(self, values = [], weights = []):
- self.values = values
- self.weights = weights
+ """ PWChoice: Random selection from , weighted by .
+ and must be the same length, but not
+ necessarily normalised.
+
+ >>> p = PWChoice([ 1, 11, 111 ], [ 8, 2, 1 ])
+ >>> p.nextn(16)
+ [111, 1, 1, 111, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1]
+ """
+
+ def __init__(self, values=[], weights=[]):
+ self.values = values
+ self.weights = weights
+
+ def next(self):
+ return wnchoice(self.values, self.weights)
- def next(self):
- return wnchoice(self.values, self.weights)
class PShuffle(Pattern):
- """ PShuffle: Shuffled list.
-
- >>> p = PShuffle([ 1, 2, 3 ])
- >>> p.nextn(16)
- [1, 3, 2, 3, 2, 1, 2, 3, 1, 2, 3, 1, 1, 2, 3, 1]
- """
- def __init__(self, values = [], repeats = sys.maxint):
- self.values = copy.copy(values)
- self.repeats = repeats
-
- self.pos = 0
- self.rcount = 1
- random.shuffle(self.values)
-
- def reset(self):
- self.pos = 0
- self.rcount = 0
- random.shuffle(self.values)
-
- Pattern.reset(self)
-
- def next(self):
- values = self.value(self.values)
- repeats = self.value(self.repeats)
-
- if self.pos >= len(values):
- if self.rcount >= repeats:
- raise StopIteration
- random.shuffle(self.values)
- self.rcount += 1
- self.pos = 0
-
- rv = values[self.pos]
- self.pos += 1
- return rv
+
+ """ PShuffle: Shuffled list.
+
+ >>> p = PShuffle([ 1, 2, 3 ])
+ >>> p.nextn(16)
+ [1, 3, 2, 3, 2, 1, 2, 3, 1, 2, 3, 1, 1, 2, 3, 1]
+ """
+
+ def __init__(self, values=[], repeats=sys.maxsize):
+ self.values = copy.copy(values)
+ self.repeats = repeats
+
+ self.pos = 0
+ self.rcount = 1
+ random.shuffle(self.values)
+
+ def reset(self):
+ self.pos = 0
+ self.rcount = 0
+ random.shuffle(self.values)
+
+ Pattern.reset(self)
+
+ def next(self):
+ values = self.value(self.values)
+ repeats = self.value(self.repeats)
+
+ if self.pos >= len(values):
+ if self.rcount >= repeats:
+ raise StopIteration
+ random.shuffle(self.values)
+ self.rcount += 1
+ self.pos = 0
+
+ rv = values[self.pos]
+ self.pos += 1
+ return rv
+
class PShuffleEvery(Pattern):
- """ PShuffleEvery: Every steps, take values from and reorder.
-
- >>> p = PShuffleEvery(PSeries(0, 1), 4)
- >>> p.nextn(16)
- """
- def __init__(self, pattern, every = 4):
- self.pattern = pattern
- self.every = every
- self.values = []
- self.pos = 0
-
- def begin(self):
- kevery = Pattern.value(self.every)
- self.values = self.pattern.nextn(kevery)
- random.shuffle(self.values)
- self.pos = 0
-
- def reset(self):
- # TODO: clarify the semantics of "reset" (which should trickle down to children)
- # vs the method called each time a new set of values is needed -- should
- # this be a flag to reset?
- self.begin()
- Pattern.reset(self)
-
- def next(self):
- if self.pos >= len(self.values):
- self.begin()
-
- rv = self.values[self.pos]
- self.pos += 1
- return rv
+
+ """ PShuffleEvery: Every steps, take values from and reorder.
+
+ >>> p = PShuffleEvery(PSeries(0, 1), 4)
+ >>> p.nextn(16)
+ """
+
+ def __init__(self, pattern, every=4):
+ self.pattern = pattern
+ self.every = every
+ self.values = []
+ self.pos = 0
+
+ def begin(self):
+ kevery = Pattern.value(self.every)
+ self.values = self.pattern.nextn(kevery)
+ random.shuffle(self.values)
+ self.pos = 0
+
+ def reset(self):
+ # TODO: clarify the semantics of "reset" (which should trickle down to children)
+ # vs the method called each time a new set of values is needed -- should
+ # this be a flag to reset?
+ self.begin()
+ Pattern.reset(self)
+
+ def next(self):
+ if self.pos >= len(self.values):
+ self.begin()
+
+ rv = self.values[self.pos]
+ self.pos += 1
+ return rv
+
class PSelfIndex(Pattern):
- def __init__(self, count = 6):
- self.pos = 0
- self.values = range(count)
- random.shuffle(self.values)
- print "init values: %s" % self.values
-
- def next(self):
- if self.pos >= len(self.values):
- # re-index values
- self.pos = 0
- self.reindex()
-
- rv = self.values[self.pos]
- self.pos += 1
-
- return rv
-
- def reindex(self):
- values_new = []
- for n in range(len(self.values)):
- values_new.append(self.values[self.values[n]])
- print "new ordering: %s" % values_new
- self.values = values_new
+
+ def __init__(self, count=6):
+ self.pos = 0
+ self.values = range(count)
+ random.shuffle(self.values)
+ print "init values: %s" % self.values
+
+ def next(self):
+ if self.pos >= len(self.values):
+ # re-index values
+ self.pos = 0
+ self.reindex()
+
+ rv = self.values[self.pos]
+ self.pos += 1
+
+ return rv
+
+ def reindex(self):
+ values_new = []
+ for n in range(len(self.values)):
+ values_new.append(self.values[self.values[n]])
+ print "new ordering: %s" % values_new
+ self.values = values_new
+
class PSkip(Pattern):
- """ PSkip: Skip events with some probability, 1 - .
- Set to False to skip events regularly.
- """
- def __init__(self, pattern, play, random = True):
- self.pattern = pattern
- self.play = play
- self.random = random
- self.pos = 0.0
-
- def next(self):
- play = self.value(self.play)
- if self.random:
- if random.uniform(0, 1) < self.play:
- return self.pattern.next()
- else:
- self.pos += play
- if self.pos >= 1:
- self.pos -= 1
- return self.pattern.next()
- return None
+
+ """ PSkip: Skip events with some probability, 1 - .
+ Set to False to skip events regularly.
+ """
+
+ def __init__(self, pattern, play, random=True):
+ self.pattern = pattern
+ self.play = play
+ self.random = random
+ self.pos = 0.0
+
+ def next(self):
+ play = self.value(self.play)
+ if self.random:
+ if random.uniform(0, 1) < self.play:
+ return self.pattern.next()
+ else:
+ self.pos += play
+ if self.pos >= 1:
+ self.pos -= 1
+ return self.pattern.next()
+ return None
+
class PFlipFlop(Pattern):
- """ PFlipFlop: flip a binary bit with some probability.
- is initial value (0 or 1)
- is chance of switching from 0->1
- is chance of switching from 1->0
-
- >>> p = PFlipFlop(0, 0.9, 0.5)
- >>> p.nextn(16)
- [1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1]
- """
- def __init__(self, initial = 0, p_on = 0.5, p_off = 0.5):
- self.value = initial
- self.p_on = p_on
- self.p_off = p_off
-
- def next(self):
- self.value = Pattern.value(self.value)
- self.p_on = Pattern.value(self.p_on)
- self.p_off = Pattern.value(self.p_off)
-
- if self.value == 0:
- if random.uniform(0, 1) < self.p_on:
- self.value = 1
- else:
- if random.uniform(0, 1) < self.p_off:
- self.value = 0
-
- return self.value
+
+ """ PFlipFlop: flip a binary bit with some probability.
+ is initial value (0 or 1)
+ is chance of switching from 0->1
+ is chance of switching from 1->0
+
+ >>> p = PFlipFlop(0, 0.9, 0.5)
+ >>> p.nextn(16)
+ [1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1]
+ """
+
+ def __init__(self, initial=0, p_on=0.5, p_off=0.5):
+ self.value = initial
+ self.p_on = p_on
+ self.p_off = p_off
+
+ def next(self):
+ self.value = Pattern.value(self.value)
+ self.p_on = Pattern.value(self.p_on)
+ self.p_off = Pattern.value(self.p_off)
+
+ if self.value == 0:
+ if random.uniform(0, 1) < self.p_on:
+ self.value = 1
+ else:
+ if random.uniform(0, 1) < self.p_off:
+ self.value = 0
+
+ return self.value
+
class PSwitchOne(Pattern):
- """ PSwitchOne: Capture input values; repeat, switching two adjacent values times.
- """
- def __init__(self, pattern, length = 4, switches = 1):
- self.pattern = pattern
- self.length = length
- self.switches = switches
-
- self.reset()
-
- def reset(self):
- self.values = []
- self.pos = 0
-
- # recursively reset my pattern fields
- Pattern.reset(self)
-
- def next(self):
- length = self.value(self.length)
- switches = self.value(self.switches)
-
- if len(self.values) < self.length:
- value = self.pattern.next()
- self.values.append(value)
- self.pos += 1
- return value
-
- if self.pos >= len(self.values):
- index = random.randint(0, len(self.values)) - 1
- indexP = (index + 1) % len(self.values)
- self.values[index], self.values[indexP] = self.values[indexP], self.values[index]
- self.pos = 0
-
- rv = self.values[self.pos]
- self.pos += 1
- return rv
+ """ PSwitchOne: Capture input values; repeat, switching two adjacent values times.
+ """
+
+ def __init__(self, pattern, length=4, switches=1):
+ self.pattern = pattern
+ self.length = length
+ self.switches = switches
+
+ self.reset()
+
+ def reset(self):
+ self.values = []
+ self.pos = 0
+
+ # recursively reset my pattern fields
+ Pattern.reset(self)
+
+ def next(self):
+ length = self.value(self.length)
+ switches = self.value(self.switches)
+
+ if len(self.values) < self.length:
+ value = self.pattern.next()
+ self.values.append(value)
+ self.pos += 1
+ return value
+
+ if self.pos >= len(self.values):
+ index = random.randint(0, len(self.values)) - 1
+ indexP = (index + 1) % len(self.values)
+ self.values[index], self.values[
+ indexP] = self.values[indexP], self.values[index]
+ self.pos = 0
+
+ rv = self.values[self.pos]
+ self.pos += 1
+ return rv
diff --git a/isobar/pattern/core.py b/isobar/pattern/core.py
index 18e4901..7f420ad 100644
--- a/isobar/pattern/core.py
+++ b/isobar/pattern/core.py
@@ -1,6 +1,6 @@
-#-------------------------------------------------------------------------------
+#-------------------------------------------------------------------------
# isobar: a python library for expressing and manipulating musical patterns.
-#-------------------------------------------------------------------------------
+#-------------------------------------------------------------------------
import sys
import copy
@@ -12,410 +12,430 @@
class Pattern:
- """ Pattern: Abstract superclass of all pattern generators.
-
- Patterns are at the core of isoar. A Pattern implements the iterator
- protocol, representing a sequence of values which are iteratively
- returned by the next() method. A pattern may be finite, after which
- point it raises an EndOfPattern exception. Call reset() to return
- a pattern to its initial state.
-
- Patterns can be subject to standard arithmetic operators as expected.
- """
-
- LENGTH_MAX = 65536
- GENO_SEPARATOR = "/"
-
- def __init__(self):
- pass
-
- def __str__(self):
- return "pattern(%s)" % self.__class__
-
- def __len__(self):
- # formerly defined as len(list(self)), but list(self) seeminly relies
- # on a correct __len__ to function as expected.
- items = self.all()
- return len(items)
-
- def __neg__(self):
- return 0 - self
-
- def __add__(self, operand):
- """Binary op: add two patterns"""
- # operand = copy.deepcopy(operand) if isinstance(operand, pattern) else PConst(operand)
- # return PAdd(copy.deepcopy(self), operand)
-
- # we actually want to retain references to our constituent patterns
- # in case the user later changes parameters of one
- operand = Pattern.pattern(operand)
- return PAdd(self, operand)
-
- def __radd__(self, operand):
- """Binary op: add two patterns"""
- return self.__add__(operand)
-
- def __sub__(self, operand):
- """Binary op: subtract two patterns"""
- operand = Pattern.pattern(operand)
- return PSub(self, operand)
-
- def __rsub__(self, operand):
- """Binary op: subtract two patterns"""
- operand = Pattern.pattern(operand)
- return PSub(operand, self)
-
- def __mul__(self, operand):
- """Binary op: multiply two patterns"""
- operand = Pattern.pattern(operand)
- return PMul(self, operand)
-
- def __rmul__(self, operand):
- """Binary op: multiply two patterns"""
- return self.__mul__(operand)
-
- def __div__(self, operand):
- """Binary op: divide two patterns"""
- operand = Pattern.pattern(operand)
- return PDiv(self, operand)
-
- def __rdiv__(self, operand):
- """Binary op: divide two patterns"""
- return self.__div__(operand)
-
- def __mod__(self, operand):
- """Modulo"""
- operand = Pattern.pattern(operand)
- return PMod(self, operand)
-
- def __rmod__(self, operand):
- """Modulo (as operand)"""
- operand = Pattern.pattern(operand)
- return operand.__mod__(self)
-
- def __rpow__(self, operand):
- """Power (as operand)"""
- operand = Pattern.pattern(operand)
- return operand.__pow__(self)
-
- def __pow__(self, operand):
- """Power"""
- operand = Pattern.pattern(operand)
- return PPow(self, operand)
-
- def __lshift__(self, operand):
- """Left bitshift"""
- operand = Pattern.pattern(operand)
- return PLShift(self, operand)
-
- def __rshift__(self, operand):
- """Right bitshift"""
- operand = Pattern.pattern(operand)
- return PRShift(self, operand)
-
- def __iter__(self):
- return self
-
- def nextn(self, count):
- rv = []
- # can't do a naive [ self.next() for n in range(count) ]
- # as we want to catch StopIterations.
- try:
- for n in range(count):
- rv.append(self.next())
- except StopIteration:
- pass
-
- return rv
-
- def next(self):
- # default pattern should be void
- raise StopIteration
-
- def all(self):
- values = []
- try:
- # do we even need a LENGTH_MAX?
- # if we omit it, .all() will become an alias for list(pattern)
- # - maybe not such a bad thing.
- for n in xrange(Pattern.LENGTH_MAX):
- value = self.next()
- values.append(value)
- except StopIteration:
- pass
-
- self.reset()
- return values
-
- def reset(self):
- """ reset a finite sequence back to position 0 """
- fields = vars(self)
- for name, field in fields.items():
- # print "reset: %s" % name
- if isinstance(field, Pattern):
- field.reset()
- # look through list items and reset anything in here too
- # (needed to reset items in PConcat)
- elif isinstance(field, list):
- for item in field:
- if isinstance(item, Pattern):
- item.reset()
- elif isinstance(field, dict):
- for item in field.values():
- if isinstance(item, Pattern):
- item.reset()
-
- def append(self, other):
- return PConcat([ self, other ])
-
- @property
- def timeline(self):
- """ returns the timeline that i am embedded in, if any """
- stack = inspect.stack()
- for frame in stack:
- frameobj = frame[0]
- args, _, _, value_dict = inspect.getargvalues(frameobj)
- if len(args) and args[0] == 'self':
- instance = value_dict.get('self', None)
- classname = instance.__class__.__name__
- if classname == "Timeline":
- return instance
-
- @staticmethod
- def fromgenotype(genotype):
- """ create a new object based on this genotype """
- print "genotype: %s" % genotype
- parts = genotype.split(Pattern.GENO_SEPARATOR)
- classname = parts[0]
- arguments = parts[1:]
- try:
- classes = vars(isobar)
- classobj = classes[classname]
- instance = classobj()
- fields = vars(instance)
- counter = 0
- for name, field in fields.items():
- instance.__dict__[name] = eval(arguments[counter])
- print "%s - %s" % (name, arguments[counter])
- counter += 1
- except Exception, e:
- print "fail: %s" % e
- pass
-
- return instance
-
- def breedWith(self, other):
- """ XXX: we should probably have a Genotype class that deals with all this """
-
- genotypeA = self.genotype()
- genotypeB = other.genotype()
- genesA = genotypeA.split("/")[1:]
- genesB = genotypeB.split("/")[1:]
- genotype = [ genotypeA.split("/")[0] ]
- for n in range(len(genesA)):
- if random.uniform(0, 1) < 0.5:
- genotype.append(genesA[n])
- else:
- genotype.append(genesB[n])
- genotypeC = Pattern.GENO_SEPARATOR.join(genotype)
- print "A %s\nB %s\n> %s" % (genotypeA, genotypeB, genotypeC)
- return Pattern.fromgenotype(genotypeC)
-
- def genotype(self):
- """ return a string representation of this pattern, suitable for breeding """
- genotype = "%s" % (self.__class__.__name__)
- fields = vars(self)
-
- import base64
-
- for name, field in fields.items():
- genotype += Pattern.GENO_SEPARATOR
-
- if isinstance(field, Pattern):
- genotype += "(%s)" % field.genotype()
- elif isinstance(field, str):
- genotype += base64.b64encode(field)
- else:
- genotype += str(field)
-
- return genotype
-
- def copy(self):
- return copy.deepcopy(self)
-
- @staticmethod
- def value(v):
- """ Resolve a pattern to its value (that is, the next item in this
- pattern, recursively).
- """
- return Pattern.value(v.next()) if isinstance(v, Pattern) else v
-
- @staticmethod
- def pattern(v):
- """ Patternify a value by wrapping it in PConst if necessary. """
- return v if isinstance(v, Pattern) else PConst(v)
+
+ """ Pattern: Abstract superclass of all pattern generators.
+
+ Patterns are at the core of isoar. A Pattern implements the iterator
+ protocol, representing a sequence of values which are iteratively
+ returned by the next() method. A pattern may be finite, after which
+ point it raises an EndOfPattern exception. Call reset() to return
+ a pattern to its initial state.
+
+ Patterns can be subject to standard arithmetic operators as expected.
+ """
+
+ LENGTH_MAX = 65536
+ GENO_SEPARATOR = "/"
+
+ def __init__(self):
+ pass
+
+ def __str__(self):
+ return "pattern(%s)" % self.__class__
+
+ def __len__(self):
+ # formerly defined as len(list(self)), but list(self) seeminly relies
+ # on a correct __len__ to function as expected.
+ items = self.all()
+ return len(items)
+
+ def __neg__(self):
+ return 0 - self
+
+ def __add__(self, operand):
+ """Binary op: add two patterns"""
+ # operand = copy.deepcopy(operand) if isinstance(operand, pattern) else PConst(operand)
+ # return PAdd(copy.deepcopy(self), operand)
+
+ # we actually want to retain references to our constituent patterns
+ # in case the user later changes parameters of one
+ operand = Pattern.pattern(operand)
+ return PAdd(self, operand)
+
+ def __radd__(self, operand):
+ """Binary op: add two patterns"""
+ return self.__add__(operand)
+
+ def __sub__(self, operand):
+ """Binary op: subtract two patterns"""
+ operand = Pattern.pattern(operand)
+ return PSub(self, operand)
+
+ def __rsub__(self, operand):
+ """Binary op: subtract two patterns"""
+ operand = Pattern.pattern(operand)
+ return PSub(operand, self)
+
+ def __mul__(self, operand):
+ """Binary op: multiply two patterns"""
+ operand = Pattern.pattern(operand)
+ return PMul(self, operand)
+
+ def __rmul__(self, operand):
+ """Binary op: multiply two patterns"""
+ return self.__mul__(operand)
+
+ def __div__(self, operand):
+ """Binary op: divide two patterns"""
+ operand = Pattern.pattern(operand)
+ return PDiv(self, operand)
+
+ def __rdiv__(self, operand):
+ """Binary op: divide two patterns"""
+ return self.__div__(operand)
+
+ def __mod__(self, operand):
+ """Modulo"""
+ operand = Pattern.pattern(operand)
+ return PMod(self, operand)
+
+ def __rmod__(self, operand):
+ """Modulo (as operand)"""
+ operand = Pattern.pattern(operand)
+ return operand.__mod__(self)
+
+ def __rpow__(self, operand):
+ """Power (as operand)"""
+ operand = Pattern.pattern(operand)
+ return operand.__pow__(self)
+
+ def __pow__(self, operand):
+ """Power"""
+ operand = Pattern.pattern(operand)
+ return PPow(self, operand)
+
+ def __lshift__(self, operand):
+ """Left bitshift"""
+ operand = Pattern.pattern(operand)
+ return PLShift(self, operand)
+
+ def __rshift__(self, operand):
+ """Right bitshift"""
+ operand = Pattern.pattern(operand)
+ return PRShift(self, operand)
+
+ def __iter__(self):
+ return self
+
+ def nextn(self, count):
+ rv = []
+ # can't do a naive [ self.next() for n in range(count) ]
+ # as we want to catch StopIterations.
+ try:
+ for n in range(count):
+ rv.append(self.next())
+ except StopIteration:
+ pass
+
+ return rv
+
+ def next(self):
+ # default pattern should be void
+ raise StopIteration
+
+ def all(self):
+ values = []
+ try:
+ # do we even need a LENGTH_MAX?
+ # if we omit it, .all() will become an alias for list(pattern)
+ # - maybe not such a bad thing.
+ for n in xrange(Pattern.LENGTH_MAX):
+ value = self.next()
+ values.append(value)
+ except StopIteration:
+ pass
+
+ self.reset()
+ return values
+
+ def reset(self):
+ """ reset a finite sequence back to position 0 """
+ fields = vars(self)
+ for name, field in fields.items():
+ # print "reset: %s" % name
+ if isinstance(field, Pattern):
+ field.reset()
+ # look through list items and reset anything in here too
+ # (needed to reset items in PConcat)
+ elif isinstance(field, list):
+ for item in field:
+ if isinstance(item, Pattern):
+ item.reset()
+ elif isinstance(field, dict):
+ for item in field.values():
+ if isinstance(item, Pattern):
+ item.reset()
+
+ def append(self, other):
+ return PConcat([self, other])
+
+ @property
+ def timeline(self):
+ """ returns the timeline that i am embedded in, if any """
+ stack = inspect.stack()
+ for frame in stack:
+ frameobj = frame[0]
+ args, _, _, value_dict = inspect.getargvalues(frameobj)
+ if len(args) and args[0] == 'self':
+ instance = value_dict.get('self', None)
+ classname = instance.__class__.__name__
+ if classname == "Timeline":
+ return instance
+
+ @staticmethod
+ def fromgenotype(genotype):
+ """ create a new object based on this genotype """
+ print "genotype: %s" % genotype
+ parts = genotype.split(Pattern.GENO_SEPARATOR)
+ classname = parts[0]
+ arguments = parts[1:]
+ try:
+ classes = vars(isobar)
+ classobj = classes[classname]
+ instance = classobj()
+ fields = vars(instance)
+ counter = 0
+ for name, field in fields.items():
+ instance.__dict__[name] = eval(arguments[counter])
+ print "%s - %s" % (name, arguments[counter])
+ counter += 1
+ except Exception as e:
+ print "fail: %s" % e
+ pass
+
+ return instance
+
+ def breedWith(self, other):
+ """ XXX: we should probably have a Genotype class that deals with all this """
+
+ genotypeA = self.genotype()
+ genotypeB = other.genotype()
+ genesA = genotypeA.split("/")[1:]
+ genesB = genotypeB.split("/")[1:]
+ genotype = [genotypeA.split("/")[0]]
+ for n in range(len(genesA)):
+ if random.uniform(0, 1) < 0.5:
+ genotype.append(genesA[n])
+ else:
+ genotype.append(genesB[n])
+ genotypeC = Pattern.GENO_SEPARATOR.join(genotype)
+ print "A %s\nB %s\n> %s" % (genotypeA, genotypeB, genotypeC)
+ return Pattern.fromgenotype(genotypeC)
+
+ def genotype(self):
+ """ return a string representation of this pattern, suitable for breeding """
+ genotype = "%s" % (self.__class__.__name__)
+ fields = vars(self)
+
+ import base64
+
+ for name, field in fields.items():
+ genotype += Pattern.GENO_SEPARATOR
+
+ if isinstance(field, Pattern):
+ genotype += "(%s)" % field.genotype()
+ elif isinstance(field, str):
+ genotype += base64.b64encode(field)
+ else:
+ genotype += str(field)
+
+ return genotype
+
+ def copy(self):
+ return copy.deepcopy(self)
+
+ @staticmethod
+ def value(v):
+ """ Resolve a pattern to its value (that is, the next item in this
+ pattern, recursively).
+ """
+ return Pattern.value(v.next()) if isinstance(v, Pattern) else v
+
+ @staticmethod
+ def pattern(v):
+ """ Patternify a value by wrapping it in PConst if necessary. """
+ return v if isinstance(v, Pattern) else PConst(v)
+
class PConst(Pattern):
- """ PConst: Pattern returning a fixed value
- >>> p = PConst(4)
- >>> p.nextn(16)
- [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
- """
- def __init__(self, constant):
- self.constant = constant
+ """ PConst: Pattern returning a fixed value
+
+ >>> p = PConst(4)
+ >>> p.nextn(16)
+ [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
+ """
+
+ def __init__(self, constant):
+ self.constant = constant
- def __str__(self):
- return "constant"
+ def __str__(self):
+ return "constant"
+
+ def next(self):
+ return self.constant
- def next(self):
- return self.constant
class PRef(Pattern):
- """ PRef: Pattern containing a reference to another pattern
- Returns the next value of the pattern contained.
- Useful to change an inner pattern in real time.
- """
- def __init__(self, pattern):
- self.pattern = pattern
- def change(self, pattern):
- self.pattern = pattern
+ """ PRef: Pattern containing a reference to another pattern
+ Returns the next value of the pattern contained.
+ Useful to change an inner pattern in real time.
+ """
+
+ def __init__(self, pattern):
+ self.pattern = pattern
+
+ def change(self, pattern):
+ self.pattern = pattern
+
+ def next(self):
+ return self.pattern.next()
- def next(self):
- return self.pattern.next()
class PFunc(Pattern):
- def __init__(self, fn):
- self.fn = fn
- def next(self):
- fn = Pattern.value(self.fn)
- return fn()
+ def __init__(self, fn):
+ self.fn = fn
+
+ def next(self):
+ fn = Pattern.value(self.fn)
+ return fn()
+
class PDict(Pattern):
- """ PDict: Dict of patterns
- Thanks to Dan Stowell
- """
- def __init__(self, value = {}):
- from isobar.pattern.sequence import PSeq
- self.dict = {}
+ """ PDict: Dict of patterns
+ Thanks to Dan Stowell
+ """
+
+ def __init__(self, value={}):
+ from isobar.pattern.sequence import PSeq
+
+ self.dict = {}
+
+ if isinstance(value, dict):
+ self.dict = value
+ elif isinstance(value, list):
+ self.dict = {}
+ try:
+ keys = value[0].keys()
+ for key in keys:
+ self.dict[key] = PSeq([item[key] for item in value], 1)
+ except IndexError:
+ pass
- if type(value) == dict:
- self.dict = value
- elif type(value) == list:
- self.dict = {}
- try:
- keys = value[0].keys()
- for key in keys:
- self.dict[key] = PSeq([ item[key] for item in value ], 1)
- except IndexError:
- pass
+ def __getitem__(self, key):
+ return self.dict[key]
- def __getitem__(self, key):
- return self.dict[key]
+ def __setitem__(self, key, value):
+ self.dict[key] = value
- def __setitem__(self, key, value):
- self.dict[key] = value
+ def __contains__(self, key):
+ return key in self.dict
- def __contains__(self, key):
- return key in self.dict
+ @classmethod
+ def load(self, filename):
+ from isobar.io.midifile import MidiFileIn
+ from isobar.pattern.sequence import PSeq
- @classmethod
- def load(self, filename):
- from isobar.io.midifile import MidiFileIn
- from isobar.pattern.sequence import PSeq
+ reader = MidiFileIn()
+ d = reader.read(filename)
+ d = dict([(key, PSeq(value, 1)) for key, value in d.items()])
+ return PDict(d)
- reader = MidiFileIn()
- d = reader.read(filename)
- d = dict([ (key, PSeq(value, 1)) for key, value in d.items() ])
- return PDict(d)
+ def has_key(self, key):
+ return key in self.dict
- def has_key(self, key):
- return key in self.dict
+ def setdefault(self, key, value):
+ if not key in self.dict:
+ self.dict[key] = value
- def setdefault(self, key, value):
- if not key in self.dict:
- self.dict[key] = value
+ def keys(self):
+ return self.dict.keys()
- def keys(self):
- return self.dict.keys()
+ def values(self):
+ return self.dict.values()
- def values(self):
- return self.dict.values()
+ def items(self):
+ return self.dict.items()
- def items(self):
- return self.dict.items()
+ def next(self):
+ vdict = Pattern.value(self.dict)
+ if not vdict:
+ raise StopIteration
- def next(self):
- vdict = Pattern.value(self.dict)
- if not vdict:
- raise StopIteration
+ # for some reason, doing a list comprehension without the surrounding square
+ # brackets causes an inner StopIteration to be suppressed -- we want to
+ # explicitly raise it.
+ rv = dict([(k, Pattern.value(vdict[k])) for k in vdict])
- # for some reason, doing a list comprehension without the surrounding square
- # brackets causes an inner StopIteration to be suppressed -- we want to
- # explicitly raise it.
- rv = dict([ (k, Pattern.value(vdict[k])) for k in vdict ])
+ return rv
- return rv
class PIndex(Pattern):
- """ PIndex: Request a specified index from an array.
- """
- def __init__(self, index, list):
- self.index = index
- self.list = list
-
- def next(self):
- index = Pattern.value(self.index)
- list = Pattern.value(self.list)
-
- #------------------------------------------------------------------
- # null indices denote a rest -- so return a null value.
- # (same behaviour as PDegree: a degree of None returns a rest.)
- #------------------------------------------------------------------
- if index is None:
- return None
- else:
- index = int(index)
- return list[index]
+
+ """ PIndex: Request a specified index from an array.
+ """
+
+ def __init__(self, index, list):
+ self.index = index
+ self.list = list
+
+ def next(self):
+ index = Pattern.value(self.index)
+ list = Pattern.value(self.list)
+
+ #------------------------------------------------------------------
+ # null indices denote a rest -- so return a null value.
+ # (same behaviour as PDegree: a degree of None returns a rest.)
+ #------------------------------------------------------------------
+ if index is None:
+ return None
+ else:
+ index = int(index)
+ return list[index]
+
class PKey(Pattern):
- """ PKey: Request a specified key from a dictionary.
- """
- def __init__(self, key, dict):
- self.key = key
- self.dict = dict
- def next(self):
- vkey = Pattern.value(self.key)
- vdict = Pattern.value(self.dict)
- return vdict[vkey]
+ """ PKey: Request a specified key from a dictionary.
+ """
+
+ def __init__(self, key, dict):
+ self.key = key
+ self.dict = dict
+
+ def next(self):
+ vkey = Pattern.value(self.key)
+ vdict = Pattern.value(self.dict)
+ return vdict[vkey]
+
class PConcat(Pattern):
- """ PConcat: Concatenate the output of multiple sequences.
-
- >>> PConcat([ PSeq([ 1, 2, 3 ], 2), PSeq([ 9, 8, 7 ], 2) ]).nextn(16)
- [1, 2, 3, 1, 2, 3, 9, 8, 7, 9, 8, 7]
- """
-
- def __init__(self, inputs):
- self.inputs = inputs
- self.current = self.inputs.pop(0)
-
- def next(self):
- try:
- return self.current.next()
- except StopIteration:
- if len(self.inputs) > 0:
- self.current = self.inputs.pop(0)
- # can't just blindly return the first value of current
- # -- what if it is empty?
- return self.next()
- else:
- # no more sequences left, so just return.
- raise StopIteration
+
+ """ PConcat: Concatenate the output of multiple sequences.
+
+ >>> PConcat([ PSeq([ 1, 2, 3 ], 2), PSeq([ 9, 8, 7 ], 2) ]).nextn(16)
+ [1, 2, 3, 1, 2, 3, 9, 8, 7, 9, 8, 7]
+ """
+
+ def __init__(self, inputs):
+ self.inputs = inputs
+ self.current = self.inputs.pop(0)
+
+ def next(self):
+ try:
+ return self.current.next()
+ except StopIteration:
+ if len(self.inputs) > 0:
+ self.current = self.inputs.pop(0)
+ # can't just blindly return the first value of current
+ # -- what if it is empty?
+ return self.next()
+ else:
+ # no more sequences left, so just return.
+ raise StopIteration
#------------------------------------------------------------------
@@ -423,87 +443,111 @@ def next(self):
#------------------------------------------------------------------
class PBinOp(Pattern):
- def __init__(self, a, b):
- self.a = a
- self.b = b
+
+ def __init__(self, a, b):
+ self.a = a
+ self.b = b
+
class PAdd(PBinOp):
- """ PAdd: Add elements of two patterns (shorthand: patternA + patternB) """
- def __str__(self):
- return "%s + %s" % (self.a, self.b)
- def next(self):
- a = self.a.next()
- b = self.b.next()
- return None if a is None or b is None else a + b
-
+ """ PAdd: Add elements of two patterns (shorthand: patternA + patternB) """
+
+ def __str__(self):
+ return "%s + %s" % (self.a, self.b)
+
+ def next(self):
+ a = self.a.next()
+ b = self.b.next()
+ return None if a is None or b is None else a + b
+
class PSub(PBinOp):
- """ PSub: Subtract elements of two patterns (shorthand: patternA - patternB) """
- def __str__(self):
- return "%s - %s" % (self.a, self.b)
- def next(self):
- a = self.a.next()
- b = self.b.next()
- return None if a is None or b is None else a - b
+ """ PSub: Subtract elements of two patterns (shorthand: patternA - patternB) """
+
+ def __str__(self):
+ return "%s - %s" % (self.a, self.b)
+
+ def next(self):
+ a = self.a.next()
+ b = self.b.next()
+ return None if a is None or b is None else a - b
+
class PMul(PBinOp):
- """ PMul: Multiply elements of two patterns (shorthand: patternA * patternB) """
- def __str__(self):
- return "(%s) * (%s)" % (self.a, self.b)
- def next(self):
- a = self.a.next()
- b = self.b.next()
- return None if a is None or b is None else a * b
+ """ PMul: Multiply elements of two patterns (shorthand: patternA * patternB) """
+
+ def __str__(self):
+ return "(%s) * (%s)" % (self.a, self.b)
+
+ def next(self):
+ a = self.a.next()
+ b = self.b.next()
+ return None if a is None or b is None else a * b
+
class PDiv(PBinOp):
- """ PDiv: Divide elements of two patterns (shorthand: patternA / patternB) """
- def __str__(self):
- return "(%s) / (%s)" % (self.a, self.b)
- def next(self):
- a = self.a.next()
- b = self.b.next()
- return None if a is None or b is None else a / b
+ """ PDiv: Divide elements of two patterns (shorthand: patternA / patternB) """
+
+ def __str__(self):
+ return "(%s) / (%s)" % (self.a, self.b)
+
+ def next(self):
+ a = self.a.next()
+ b = self.b.next()
+ return None if a is None or b is None else a / b
+
class PMod(PBinOp):
- """ PMod: Modulo elements of two patterns (shorthand: patternA % patternB) """
- def __str__(self):
- return "(%s) %% (%s)" % (self.a, self.b)
- def next(self):
- a = self.a.next()
- b = self.b.next()
- return None if a is None or b is None else a % b
+ """ PMod: Modulo elements of two patterns (shorthand: patternA % patternB) """
+
+ def __str__(self):
+ return "(%s) %% (%s)" % (self.a, self.b)
+
+ def next(self):
+ a = self.a.next()
+ b = self.b.next()
+ return None if a is None or b is None else a % b
+
class PPow(PBinOp):
- """ PPow: One pattern to the power of another (shorthand: patternA ** patternB) """
- def __str__(self):
- return "pow(%s, %s)" % (self.a, self.b)
- def next(self):
- a = self.a.next()
- b = self.b.next()
- return None if a is None or b is None else pow(a, b)
+ """ PPow: One pattern to the power of another (shorthand: patternA ** patternB) """
+
+ def __str__(self):
+ return "pow(%s, %s)" % (self.a, self.b)
+
+ def next(self):
+ a = self.a.next()
+ b = self.b.next()
+ return None if a is None or b is None else pow(a, b)
+
class PLShift(PBinOp):
- """ PLShift: Binary left-shift (shorthand: patternA << patternB) """
- def __str__(self):
- return "(%s << %s)" % (self.a, self.b)
- def next(self):
- a = self.a.next()
- b = self.b.next()
- return None if a is None or b is None else a << b
+ """ PLShift: Binary left-shift (shorthand: patternA << patternB) """
+
+ def __str__(self):
+ return "(%s << %s)" % (self.a, self.b)
+
+ def next(self):
+ a = self.a.next()
+ b = self.b.next()
+ return None if a is None or b is None else a << b
+
class PRShift(PBinOp):
- """ PRShift: Binary right-shift (shorthand: patternA << patternB) """
- def __str__(self):
- return "(%s >> %s)" % (self.a, self.b)
-
- def next(self):
- a = self.a.next()
- b = self.b.next()
- return None if a is None or b is None else a >> b
+
+ """ PRShift: Binary right-shift (shorthand: patternA << patternB) """
+
+ def __str__(self):
+ return "(%s >> %s)" % (self.a, self.b)
+
+ def next(self):
+ a = self.a.next()
+ b = self.b.next()
+ return None if a is None or b is None else a >> b
diff --git a/isobar/pattern/fade.py b/isobar/pattern/fade.py
index e0bf518..a5c661b 100644
--- a/isobar/pattern/fade.py
+++ b/isobar/pattern/fade.py
@@ -9,127 +9,141 @@
from isobar.pattern.core import *
+
class PFade(Pattern):
- IN = 1
- PEAK = 0
- OUT = -1
+ IN = 1
+ PEAK = 0
+ OUT = -1
+
+ def __init__(self):
+ self.direction = 1
- def __init__(self):
- self.direction = 1
+ self.fadestep = 0
+ self.counter = 0
+ self.rcounter = 0
+ self.pattern = None
- self.fadestep = 0
- self.counter = 0
- self.rcounter = 0
- self.pattern = None
+ def __str__(self):
+ classname = str(p.__class__).split(".")[-1]
+ return "%s(%s)" % (classname, str(self.pattern))
- def __str__(self):
- classname = str(p.__class__).split(".")[-1]
- return "%s(%s)" % (classname, str(self.pattern))
class PFadeNotewise(PFade):
- """ PFadeNotewise: Fade a pattern in/out by introducing notes at a gradual rate. """
-
- def __init__(self, pattern, rate_min = 1, rate_max = 1, repeats = 1, repeats_postfade = 1):
- PFade.__init__(self)
-
- self.notes = pattern.all()
- self.on = [ False ] * len(self.notes)
- self.rate_min = rate_min
- self.rate_max = rate_max
- self.repeats = repeats
- self.repeats_postfade = repeats_postfade
-
- self.fadeindex = 0
-
- def fade_in(self):
- fade_count = random.randint(self.rate_min, self.rate_max)
- for n in range(fade_count):
- if self.fadeindex >= len(self.on):
- return
- self.on[self.fadeindex] = True
- self.fadeindex += 1
- self.fadestep += 1
-
- def fade_out(self):
- fade_count = random.randint(self.rate_min, self.rate_max)
- for n in range(fade_count):
- if self.fadeindex >= len(self.on):
- return
- self.on[self.fadeindex] = False
- self.fadeindex += 1
- self.fadestep -= 1
-
- def next(self):
- if self.counter >= len(self.notes):
- #----------------------------------------------------------------------
- # we've reached the end of the sequence.
- #----------------------------------------------------------------------
- self.rcounter += 1
- self.counter = 0
- if self.direction == PFade.IN and self.rcounter == self.repeats:
- #----------------------------------------------------------------------
- # we've finished fading in -- now play the complete sequence.
- #----------------------------------------------------------------------
- self.rcounter = 0
- self.fade_in()
- if self.fadestep == len(self.notes):
- self.direction = PFade.PEAK
- elif self.direction == PFade.OUT and self.rcounter == self.repeats:
- #----------------------------------------------------------------------
- # finish fading out.
- #----------------------------------------------------------------------
- self.rcounter = 0
- self.fade_out()
- if self.fadestep == 0:
- raise StopIteration
- elif self.direction == PFade.PEAK and self.rcounter == self.repeats_postfade:
- #----------------------------------------------------------------------
- # start fading out.
- #----------------------------------------------------------------------
- self.rcounter = 0
- self.direction = PFade.OUT
- self.fadeindex = 0
- self.fade_out()
-
- if type(self.notes[self.counter]) == dict:
- if self.on[self.counter]:
- rv = self.notes[self.counter]
- else:
- note = self.notes[self.counter].copy()
- note["note"] = None
- rv = note
- else:
- rv = self.notes[self.counter] if self.on[self.counter] else None
-
- self.counter += 1
-
- return rv
+
+ """ PFadeNotewise: Fade a pattern in/out by introducing notes at a gradual rate. """
+
+ def __init__(
+ self,
+ pattern,
+ rate_min=1,
+ rate_max=1,
+ repeats=1,
+ repeats_postfade=1):
+ PFade.__init__(self)
+
+ self.notes = pattern.all()
+ self.on = [False] * len(self.notes)
+ self.rate_min = rate_min
+ self.rate_max = rate_max
+ self.repeats = repeats
+ self.repeats_postfade = repeats_postfade
+
+ self.fadeindex = 0
+
+ def fade_in(self):
+ fade_count = random.randint(self.rate_min, self.rate_max)
+ for n in range(fade_count):
+ if self.fadeindex >= len(self.on):
+ return
+ self.on[self.fadeindex] = True
+ self.fadeindex += 1
+ self.fadestep += 1
+
+ def fade_out(self):
+ fade_count = random.randint(self.rate_min, self.rate_max)
+ for n in range(fade_count):
+ if self.fadeindex >= len(self.on):
+ return
+ self.on[self.fadeindex] = False
+ self.fadeindex += 1
+ self.fadestep -= 1
+
+ def next(self):
+ if self.counter >= len(self.notes):
+ #------------------------------------------------------------------
+ # we've reached the end of the sequence.
+ #------------------------------------------------------------------
+ self.rcounter += 1
+ self.counter = 0
+ if self.direction == PFade.IN and self.rcounter == self.repeats:
+ #--------------------------------------------------------------
+ # we've finished fading in -- now play the complete sequence.
+ #--------------------------------------------------------------
+ self.rcounter = 0
+ self.fade_in()
+ if self.fadestep == len(self.notes):
+ self.direction = PFade.PEAK
+ elif self.direction == PFade.OUT and self.rcounter == self.repeats:
+ #--------------------------------------------------------------
+ # finish fading out.
+ #--------------------------------------------------------------
+ self.rcounter = 0
+ self.fade_out()
+ if self.fadestep == 0:
+ raise StopIteration
+ elif self.direction == PFade.PEAK and self.rcounter == self.repeats_postfade:
+ #--------------------------------------------------------------
+ # start fading out.
+ #--------------------------------------------------------------
+ self.rcounter = 0
+ self.direction = PFade.OUT
+ self.fadeindex = 0
+ self.fade_out()
+
+ if isinstance(self.notes[self.counter], dict):
+ if self.on[self.counter]:
+ rv = self.notes[self.counter]
+ else:
+ note = self.notes[self.counter].copy()
+ note["note"] = None
+ rv = note
+ else:
+ rv = self.notes[self.counter] if self.on[self.counter] else None
+
+ self.counter += 1
+
+ return rv
+
class PFadeNotewiseRandom(PFadeNotewise):
- """ PFadeNotewise: Fade a pattern in/out by gradually introducing random notes. """
- def __init__(self, *args, **kwargs):
- PFadeNotewise.__init__(self, *args, **kwargs)
- self.ordering = range(len(self.notes))
- random.shuffle(self.ordering)
-
- def fade_in(self):
- fade_count = random.randint(self.rate_min, self.rate_max)
- if fade_count < 1: fade_count = 1
- for n in range(fade_count):
- if self.fadeindex >= len(self.on):
- return
- index = self.ordering[self.fadeindex]
- self.on[index] = True
- self.fadeindex += 1
- self.fadestep += 1
-
- def fade_out(self):
- fade_count = random.randint(self.rate_min, self.rate_max)
- if fade_count < 1: fade_count = 1
- for n in range(fade_count):
- if self.fadeindex >= len(self.on):
- return
- index = self.ordering[self.fadeindex]
- self.on[index] = False
- self.fadeindex += 1
- self.fadestep -= 1
+
+ """ PFadeNotewise: Fade a pattern in/out by gradually introducing random notes. """
+
+ def __init__(self, *args, **kwargs):
+ PFadeNotewise.__init__(self, *args, **kwargs)
+ self.ordering = range(len(self.notes))
+ random.shuffle(self.ordering)
+
+ def fade_in(self):
+ fade_count = random.randint(self.rate_min, self.rate_max)
+ if fade_count < 1:
+ fade_count = 1
+ for n in range(fade_count):
+ if self.fadeindex >= len(self.on):
+ return
+ index = self.ordering[self.fadeindex]
+ self.on[index] = True
+ self.fadeindex += 1
+ self.fadestep += 1
+
+ def fade_out(self):
+ fade_count = random.randint(self.rate_min, self.rate_max)
+ if fade_count < 1:
+ fade_count = 1
+ for n in range(fade_count):
+ if self.fadeindex >= len(self.on):
+ return
+ index = self.ordering[self.fadeindex]
+ self.on[index] = False
+ self.fadeindex += 1
+ self.fadestep -= 1
diff --git a/isobar/pattern/grapher.py b/isobar/pattern/grapher.py
index efef24a..82f5e75 100644
--- a/isobar/pattern/grapher.py
+++ b/isobar/pattern/grapher.py
@@ -2,7 +2,9 @@
from isobar.util import *
import sys
+
class Grapher:
+
def graph(self, markov):
gv = GvGen()
items = []
@@ -17,7 +19,7 @@ def graph(self, markov):
# first pass: add nodes
for n, node in enumerate(markov.nodes):
- if type(node) == int:
+ if isinstance(node, int):
node = miditopitch(node)
item = gv.newItem(str(node))
items.append(item)
@@ -31,5 +33,4 @@ def graph(self, markov):
gv.styleApply("edge", link)
# gv.propertyAppend(link, "weight", edges[e])
-
gv.dot(sys.stderr)
diff --git a/isobar/pattern/harmony.py b/isobar/pattern/harmony.py
index c985f4e..ae186c0 100644
--- a/isobar/pattern/harmony.py
+++ b/isobar/pattern/harmony.py
@@ -1,24 +1,28 @@
from isobar.pattern.core import *
+
class PFilterByKey(Pattern):
- def __init__(self, input, key):
- self.input = input
- self.key = key
-
- def next(self):
- note = self.input.next()
- key = Pattern.value(self.key)
- if note in key:
- return note
- else:
- return None
+
+ def __init__(self, input, key):
+ self.input = input
+ self.key = key
+
+ def next(self):
+ note = self.input.next()
+ key = Pattern.value(self.key)
+ if note in key:
+ return note
+ else:
+ return None
+
class PNearest(Pattern):
- def __init__(self, input, key):
- self.input = input
- self.key = key
-
- def next(self):
- note = self.input.next()
- key = Pattern.value(self.key)
- return key.nearest_note(note)
+
+ def __init__(self, input, key):
+ self.input = input
+ self.key = key
+
+ def next(self):
+ note = self.input.next()
+ key = Pattern.value(self.key)
+ return key.nearest_note(note)
diff --git a/isobar/pattern/lsystem.py b/isobar/pattern/lsystem.py
index a0dce06..0946a2a 100644
--- a/isobar/pattern/lsystem.py
+++ b/isobar/pattern/lsystem.py
@@ -3,75 +3,80 @@
from isobar.note import *
from isobar.pattern import *
+
class LSystem:
- def __init__(self, rule = "N[-N++N]-N", seed = "N"):
- self.rule = rule
- self.seed = seed
- self.string = seed
-
- self.reset()
-
- def iterate(self, count = 3):
- if self.rule.count("[") != self.rule.count("]"):
- raise ValueError, "Imbalanced brackets in rule string: %s" % self.rule
-
- for n in range(count):
- string_new = ""
- for char in self.string:
- string_new = string_new + self.rule if char == "N" else string_new + char
-
- self.string = string_new
- # print "(iter %d) string now %s" % (n, self.string)
-
- def next(self):
- while self.pos < len(self.string):
- token = self.string[self.pos]
- self.pos = self.pos + 1
-
- if token == 'N':
- return self.state
- elif token == '_':
- return None
- elif token == '-':
- self.state -= 1
- elif token == '+':
- self.state += 1
- elif token == '?':
- self.state += random.choice([ -1, 1 ])
- elif token == '[':
- self.stack.append(self.state)
- elif token == ']':
- self.state = self.stack.pop()
-
- raise StopIteration
-
- def reset(self):
- self.pos = 0
- self.stack = []
- self.state = 0
+
+ def __init__(self, rule="N[-N++N]-N", seed="N"):
+ self.rule = rule
+ self.seed = seed
+ self.string = seed
+
+ self.reset()
+
+ def iterate(self, count=3):
+ if self.rule.count("[") != self.rule.count("]"):
+ raise ValueError(
+ "Imbalanced brackets in rule string: %s" %
+ self.rule)
+
+ for n in range(count):
+ string_new = ""
+ for char in self.string:
+ string_new = string_new + \
+ self.rule if char == "N" else string_new + char
+
+ self.string = string_new
+ # print "(iter %d) string now %s" % (n, self.string)
+
+ def next(self):
+ while self.pos < len(self.string):
+ token = self.string[self.pos]
+ self.pos = self.pos + 1
+
+ if token == 'N':
+ return self.state
+ elif token == '_':
+ return None
+ elif token == '-':
+ self.state -= 1
+ elif token == '+':
+ self.state += 1
+ elif token == '?':
+ self.state += random.choice([-1, 1])
+ elif token == '[':
+ self.stack.append(self.state)
+ elif token == ']':
+ self.state = self.stack.pop()
+
+ raise StopIteration
+
+ def reset(self):
+ self.pos = 0
+ self.stack = []
+ self.state = 0
class PLSys(Pattern):
- """ PLSys: integer sequence derived from Lindenmayer systems """
- def __init__(self, rule, depth = 3, loop = True):
- self.rule = rule
- self.depth = depth
- self.loop = loop
- self.reset()
+ """ PLSys: integer sequence derived from Lindenmayer systems """
- def __str__(self):
- return "lsystem (%s)" % rule
+ def __init__(self, rule, depth=3, loop=True):
+ self.rule = rule
+ self.depth = depth
+ self.loop = loop
+ self.reset()
- def reset(self):
- self.lsys = LSystem(self.rule, "N")
- self.lsys.iterate(self.depth)
+ def __str__(self):
+ return "lsystem (%s)" % rule
- def next(self):
- n = self.lsys.next()
- if self.loop and n is None:
- self.lsys.reset()
- n = self.lsys.next()
+ def reset(self):
+ self.lsys = LSystem(self.rule, "N")
+ self.lsys.iterate(self.depth)
- return None if n is None else n
+ def next(self):
+ n = self.lsys.next()
+ if self.loop and n is None:
+ self.lsys.reset()
+ n = self.lsys.next()
+ return None if n is None else n
diff --git a/isobar/pattern/markov.py b/isobar/pattern/markov.py
index 86b6850..91f96e8 100644
--- a/isobar/pattern/markov.py
+++ b/isobar/pattern/markov.py
@@ -1,107 +1,113 @@
from isobar.pattern import *
from isobar.util import *
+
class PMarkov(Pattern):
- """ PMarkov: First-order Markov chain.
- http://pastebin.com/rNu2CSFs
- TODO: Implement n-order. """
-
- def __init__(self, nodes = None):
- #------------------------------------------------------------------------
- # avoid using [] (mutable default arguments considered harmful)
- # http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument
- #------------------------------------------------------------------------
- if isinstance(nodes, list):
- #------------------------------------------------------------------------
- # Learn a sequence of values by inferring probabilities
- #------------------------------------------------------------------------
- learner = MarkovLearner()
- for value in nodes:
- learner.register(value)
- self.nodes = learner.markov.nodes
- elif isinstance(nodes, dict):
- #------------------------------------------------------------------------
- # Take a dictionary argument with the same format as our internal nodes
- # model : eg { 1 : [ 2, 3 ], 2 : [ 3 ], 3 : [ 1, 2 ] }
- #------------------------------------------------------------------------
- self.nodes = nodes
- else:
- self.nodes = {}
-
- self.node = None
-
- def randomize(self):
- """ Uses the existing set of nodes but randomizes their connections. """
- for node in self.nodes.keys():
- self.nodes[node] = []
- for other in self.nodes.keys():
- prob = random.randint(0, 10)
- self.nodes[node] += [ other ] * prob
-
- def next(self):
- #------------------------------------------------------------------------
- # Returns the next value according to our internal statistical model.
- #------------------------------------------------------------------------
- if self.node is None and len(self.nodes) > 0:
- self.node = random.choice(self.nodes.keys())
- else:
- try:
- #------------------------------------------------------------------------
- #
- #------------------------------------------------------------------------
- self.node = random.choice(self.nodes[self.node])
- except IndexError:
- self.node = random.choice(self.nodes.keys())
- except KeyError:
- print "no such node: %s" % self.node
-
- if self.node is None:
- print "PMarkov: got no next node :-("
-
- return self.node
-
- @classmethod
- def fromsequence(self, sequence):
- learner = MarkovLearner()
- for value in sequence:
- learner.register(value)
- return PMarkov(learner.markov)
-
- @classmethod
- def fromscale(self, scale):
- # TODO: BROKEN
- semitones = scale.semitones
- weights = scale.weights
- return PMarkov(semitones, [ weights[:] for _ in semitones ])
+
+ """ PMarkov: First-order Markov chain.
+ http://pastebin.com/rNu2CSFs
+ TODO: Implement n-order. """
+
+ def __init__(self, nodes=None):
+ #----------------------------------------------------------------------
+ # avoid using [] (mutable default arguments considered harmful)
+ # http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument
+ #----------------------------------------------------------------------
+ if isinstance(nodes, list):
+ #------------------------------------------------------------------
+ # Learn a sequence of values by inferring probabilities
+ #------------------------------------------------------------------
+ learner = MarkovLearner()
+ for value in nodes:
+ learner.register(value)
+ self.nodes = learner.markov.nodes
+ elif isinstance(nodes, dict):
+ #------------------------------------------------------------------
+ # Take a dictionary argument with the same format as our internal nodes
+ # model : eg { 1 : [ 2, 3 ], 2 : [ 3 ], 3 : [ 1, 2 ] }
+ #------------------------------------------------------------------
+ self.nodes = nodes
+ else:
+ self.nodes = {}
+
+ self.node = None
+
+ def randomize(self):
+ """ Uses the existing set of nodes but randomizes their connections. """
+ for node in self.nodes.keys():
+ self.nodes[node] = []
+ for other in self.nodes.keys():
+ prob = random.randint(0, 10)
+ self.nodes[node] += [other] * prob
+
+ def next(self):
+ #----------------------------------------------------------------------
+ # Returns the next value according to our internal statistical model.
+ #----------------------------------------------------------------------
+ if self.node is None and len(self.nodes) > 0:
+ self.node = random.choice(self.nodes.keys())
+ else:
+ try:
+ #--------------------------------------------------------------
+ #
+ #--------------------------------------------------------------
+ self.node = random.choice(self.nodes[self.node])
+ except IndexError:
+ self.node = random.choice(self.nodes.keys())
+ except KeyError:
+ print "no such node: %s" % self.node
+
+ if self.node is None:
+ print "PMarkov: got no next node :-("
+
+ return self.node
+
+ @classmethod
+ def fromsequence(self, sequence):
+ learner = MarkovLearner()
+ for value in sequence:
+ learner.register(value)
+ return PMarkov(learner.markov)
+
+ @classmethod
+ def fromscale(self, scale):
+ # TODO: BROKEN
+ semitones = scale.semitones
+ weights = scale.weights
+ return PMarkov(semitones, [weights[:] for _ in semitones])
+
class MarkovLearner:
- def __init__(self):
- self.markov = PMarkov()
- self.last = None
-
- def register(self, value):
- # print "registering %s in %s (%s)" % (value, self.markov, self.markov.nodes)
- if value not in self.markov.nodes:
- self.markov.nodes[value] = []
- if self.last is not None:
- self.markov.nodes[self.last].append(value)
- self.last = value
+
+ def __init__(self):
+ self.markov = PMarkov()
+ self.last = None
+
+ def register(self, value):
+ # print "registering %s in %s (%s)" % (value, self.markov,
+ # self.markov.nodes)
+ if value not in self.markov.nodes:
+ self.markov.nodes[value] = []
+ if self.last is not None:
+ self.markov.nodes[self.last].append(value)
+ self.last = value
+
class MarkovLParallel:
- def __init__(self, count):
- self.count = count
- self.learners = map(lambda n: MarkovLearner(), range(count))
- def register(self, list):
- for n in range(self.count):
- print "registering %d in %s" % (list[n], self.learners[n])
- self.learners[n].register(list[n])
+ def __init__(self, count):
+ self.count = count
+ self.learners = map(lambda n: MarkovLearner(), range(count))
- def normalize(self):
- for learner in self.learners:
- learner.markov.normalize()
+ def register(self, list):
+ for n in range(self.count):
+ print "registering %d in %s" % (list[n], self.learners[n])
+ self.learners[n].register(list[n])
- def chains(self):
- self.normalize()
- return map(lambda learner: learner.markov, self.learners)
+ def normalize(self):
+ for learner in self.learners:
+ learner.markov.normalize()
+ def chains(self):
+ self.normalize()
+ return map(lambda learner: learner.markov, self.learners)
diff --git a/isobar/pattern/operator.py b/isobar/pattern/operator.py
index e5bb85a..9589562 100644
--- a/isobar/pattern/operator.py
+++ b/isobar/pattern/operator.py
@@ -8,273 +8,316 @@
from isobar.key import *
from isobar.util import *
+
class PChanged(Pattern):
- """ PChanged: Outputs a 1 if the value of a pattern has changed. """
- def __init__(self, source):
- self.source = source
- self.current = Pattern.value(self.source)
+ """ PChanged: Outputs a 1 if the value of a pattern has changed. """
+
+ def __init__(self, source):
+ self.source = source
+ self.current = Pattern.value(self.source)
+
+ def next(self):
+ next = Pattern.value(self.source)
+ rv = 0 if next == self.current else 1
+ self.current = next
+ return rv
- def next(self):
- next = Pattern.value(self.source)
- rv = 0 if next == self.current else 1
- self.current = next
- return rv
class PDiff(Pattern):
- """ PDiff: Outputs the difference between the current and previous values of an input pattern """
- def __init__(self, source):
- self.source = source
- self.current = Pattern.value(self.source)
+ """ PDiff: Outputs the difference between the current and previous values of an input pattern """
+
+ def __init__(self, source):
+ self.source = source
+ self.current = Pattern.value(self.source)
+
+ def next(self):
+ next = Pattern.value(self.source)
+ rv = next - self.current
+ self.current = next
+ return rv
- def next(self):
- next = Pattern.value(self.source)
- rv = next - self.current
- self.current = next
- return rv
class PAbs(Pattern):
- """ PAbs: Absolute value of """
- def __init__(self, input):
- self.input = input
+ """ PAbs: Absolute value of """
+
+ def __init__(self, input):
+ self.input = input
+
+ def next(self):
+ next = Pattern.value(self.input)
+ if next is not None:
+ return abs(next)
+ return next
- def next(self):
- next = Pattern.value(self.input)
- if next is not None:
- return abs(next)
- return next
class PNorm(Pattern):
- """ PNorm: Normalise to [0..1].
- Use maximum and minimum values found in history of size .
- """
-
- def __init__(self, input, window_size = None):
- self.input = input
- self.window_size = window_size
-
- self.lower = None
- self.upper = None
- self.history = []
-
- def next(self):
- value = Pattern.value(self.input)
- window_size = Pattern.value(self.window_size)
-
- if window_size:
- # TODO
- pass
- else:
- if self.lower is None:
- self.lower = value
- self.upper = value
- else:
- if value > self.upper:
- self.upper = value
- if value < self.lower:
- self.lower = value
-
- if self.upper == self.lower:
- rv = 0.0
- else:
- rv = (value - self.lower) / (self.upper - self.lower)
-
- return rv;
+
+ """ PNorm: Normalise to [0..1].
+ Use maximum and minimum values found in history of size .
+ """
+
+ def __init__(self, input, window_size=None):
+ self.input = input
+ self.window_size = window_size
+
+ self.lower = None
+ self.upper = None
+ self.history = []
+
+ def next(self):
+ value = Pattern.value(self.input)
+ window_size = Pattern.value(self.window_size)
+
+ if window_size:
+ # TODO
+ pass
+ else:
+ if self.lower is None:
+ self.lower = value
+ self.upper = value
+ else:
+ if value > self.upper:
+ self.upper = value
+ if value < self.lower:
+ self.lower = value
+
+ if self.upper == self.lower:
+ rv = 0.0
+ else:
+ rv = (value - self.lower) / (self.upper - self.lower)
+
+ return rv
+
class PCollapse(Pattern):
- """ PCollapse: Skip over any rests in """
- def __init__(self, input):
- self.input = input
- def next(self):
- rv = None
- while rv == None:
- rv = Pattern.value(self.input)
- return rv
+ """ PCollapse: Skip over any rests in """
+
+ def __init__(self, input):
+ self.input = input
+
+ def next(self):
+ rv = None
+ while rv is None:
+ rv = Pattern.value(self.input)
+ return rv
+
class PNoRepeats(Pattern):
- """ PNoRepeats: Skip over repeated values in """
- def __init__(self, input):
- self.input = input
- self.value = sys.maxint
-
- def next(self):
- rv = sys.maxint
- while rv == self.value or rv == sys.maxint:
- rv = Pattern.value(self.input)
- self.value = rv
- return rv
+
+ """ PNoRepeats: Skip over repeated values in """
+
+ def __init__(self, input):
+ self.input = input
+ self.value = sys.maxsize
+
+ def next(self):
+ rv = sys.maxsize
+ while rv == self.value or rv == sys.maxsize:
+ rv = Pattern.value(self.input)
+ self.value = rv
+ return rv
+
class PReverse(Pattern):
- """ reverses a finite sequence """
- def __init__(self, input):
- self.input = input
- self.values = reversed(list(input))
+ """ reverses a finite sequence """
+
+ def __init__(self, input):
+ self.input = input
+ self.values = reversed(list(input))
+
+ def next(self):
+ return self.values.next()
- def next(self):
- return self.values.next()
class PDelay(Pattern):
- """ outputs the next value of patternA after patternB ticks """
- def __init__(self, source, delay):
- self.source = source
- self.delay = delay
- self.counter = Pattern.value(self.delay)
+ """ outputs the next value of patternA after patternB ticks """
+
+ def __init__(self, source, delay):
+ self.source = source
+ self.delay = delay
+ self.counter = Pattern.value(self.delay)
+
+ def next(self):
+ self.counter -= 1
+ if self.counter < 0:
+ self.counter = Pattern.value(self.delay)
+ return Pattern.value(self.source)
- def next(self):
- self.counter -= 1
- if self.counter < 0:
- self.counter = Pattern.value(self.delay)
- return Pattern.value(self.source)
class PMap(Pattern):
- """ PMap: Apply an arbitrary function to an input pattern.
- Will pass any additional arguments, which can also be patterns.
- >>> PMap(PSeries(), lambda value: value * value).nextn(16)
- [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225]
+ """ PMap: Apply an arbitrary function to an input pattern.
+ Will pass any additional arguments, which can also be patterns.
+
+ >>> PMap(PSeries(), lambda value: value * value).nextn(16)
+ [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225]
+
+ >>> PMap(PSeries(), pow, PSeries()).nextn(16)
+ [1, 1, 4, 27, 256, 3125, 46656, 823543, 16777216, 387420489, 10000000000, ... ]
+ """
- >>> PMap(PSeries(), pow, PSeries()).nextn(16)
- [1, 1, 4, 27, 256, 3125, 46656, 823543, 16777216, 387420489, 10000000000, ... ]
- """
+ def __init__(self, input, operator, *args, **kwargs):
+ self.input = input
+ self.operator = operator
+ self.args = args
+ self.kwargs = kwargs
- def __init__(self, input, operator, *args, **kwargs):
- self.input = input
- self.operator = operator
- self.args = args
- self.kwargs = kwargs
+ def next(self):
+ args = [Pattern.value(value) for value in self.args]
+ kwargs = dict((key, Pattern.value(value))
+ for key, value in self.kwargs.items())
+ value = self.input.next()
+ rv = self.operator(value, *args, **kwargs)
+ return rv
- def next(self):
- args = [ Pattern.value(value) for value in self.args ]
- kwargs = dict((key, Pattern.value(value)) for key, value in self.kwargs.items())
- value = self.input.next()
- rv = self.operator(value, *args, **kwargs)
- return rv
class PMapEnumerated(PMap):
- """ PMapEnumerated: Apply arbitrary function to input, passing a counter.
-
- >>> PMapEnumerated(PSeq([ 1, 11, 111 ]), lambda n, value: n * value).nextn(16)
- [0, 11, 222, 3, 44, 555, 6, 77, 888, 9, 110, 1221, 12, 143, 1554, 15]
- """
-
- def __init__(self, *args):
- PMap.__init__(self, *args)
- self.counter = 0
-
- def next(self):
- args = [ Pattern.value(value) for value in self.args ]
- kwargs = dict((key, Pattern.value(value)) for key, value in self.kwargs.items())
- value = self.input.next()
- rv = self.operator(self.counter, value, *args, **kwargs)
- self.counter += 1
- return rv
-
+
+ """ PMapEnumerated: Apply arbitrary function to input, passing a counter.
+
+ >>> PMapEnumerated(PSeq([ 1, 11, 111 ]), lambda n, value: n * value).nextn(16)
+ [0, 11, 222, 3, 44, 555, 6, 77, 888, 9, 110, 1221, 12, 143, 1554, 15]
+ """
+
+ def __init__(self, *args):
+ PMap.__init__(self, *args)
+ self.counter = 0
+
+ def next(self):
+ args = [Pattern.value(value) for value in self.args]
+ kwargs = dict((key, Pattern.value(value))
+ for key, value in self.kwargs.items())
+ value = self.input.next()
+ rv = self.operator(self.counter, value, *args, **kwargs)
+ self.counter += 1
+ return rv
+
+
class PLinLin(PMap):
- """ PLinLin: Map from linear range [a,b] to linear range [c,d].
- >>> p = PLinLin(PWhite(), 0, 1, -50, 50)
- >>> p.nextn(16)
- [-34.434991496625955, -33.38823791706497, 42.153457333940267, 16.692545937573783, ... -48.850511242044604 ]
- """
+ """ PLinLin: Map from linear range [a,b] to linear range [c,d].
- def linlin(self, value, from_min = 0, from_max = 1, to_min = 0, to_max = 1):
- norm = float(value - from_min) / (from_max - from_min)
- return norm * float(to_max - to_min) + to_min
+ >>> p = PLinLin(PWhite(), 0, 1, -50, 50)
+ >>> p.nextn(16)
+ [-34.434991496625955, -33.38823791706497, 42.153457333940267, 16.692545937573783, ... -48.850511242044604 ]
+ """
+
+ def linlin(self, value, from_min=0, from_max=1, to_min=0, to_max=1):
+ norm = float(value - from_min) / (from_max - from_min)
+ return norm * float(to_max - to_min) + to_min
+
+ def __init__(self, input, *args):
+ PMap.__init__(self, input, self.linlin, *args)
- def __init__(self, input, *args):
- PMap.__init__(self, input, self.linlin, *args)
class PLinExp(PMap):
- """ PLinExp: Map from linear range [a,b] to exponential range [c,d].
- >>> p = PLinExp(PWhite(), 0, 1, 40, 20000)
- >>> p.nextn(16)
- """
+ """ PLinExp: Map from linear range [a,b] to exponential range [c,d].
+
+ >>> p = PLinExp(PWhite(), 0, 1, 40, 20000)
+ >>> p.nextn(16)
+ """
- def linexp(self, value, from_min = 0, from_max = 1, to_min = 1, to_max = 10):
- if value < from_min: return to_min
- if value > from_max: return to_max
- return ((to_max / to_min) ** ((value - from_min) / (from_max - from_min))) * to_min;
+ def linexp(self, value, from_min=0, from_max=1, to_min=1, to_max=10):
+ if value < from_min:
+ return to_min
+ if value > from_max:
+ return to_max
+ return (
+ (to_max / to_min) ** ((value - from_min) / (from_max - from_min))) * to_min
+
+ def __init__(self, input, *args):
+ PMap.__init__(self, input, self.linexp, *args)
- def __init__(self, input, *args):
- PMap.__init__(self, input, self.linexp, *args)
class PRound(PMap):
- """ PRound: Round to N decimal places.
- >>> p = PLinExp(PWhite(), 0, 1, 40, 20000)
- >>> p.nextn(16)
- """
- def __init__(self, input, *args):
- PMap.__init__(self, input, round, *args)
+ """ PRound: Round to N decimal places.
+
+ >>> p = PLinExp(PWhite(), 0, 1, 40, 20000)
+ >>> p.nextn(16)
+ """
+
+ def __init__(self, input, *args):
+ PMap.__init__(self, input, round, *args)
+
class PIndexOf(Pattern):
- """ PIndexOf: Find index of items from in
-
- >>> p = PIndexOf([ chr(ord("a") + n) for n in range(26) ], PSeq("isobar"))
- >>> p.nextn(16)
- >>> [8, 18, 14, 1, 0, 17, 8, 18, 14, 1, 0, 17, 8, 18, 14, 1]
- """
- def __init__(self, list, item):
- self.list = list
- self.item = item
-
- def next(self):
- list = Pattern.value(self.list)
- item = Pattern.value(self.item)
- if list is None or item is None or item not in list:
- return None
- return list.index(item)
+
+ """ PIndexOf: Find index of items from in
+
+ >>> p = PIndexOf([ chr(ord("a") + n) for n in range(26) ], PSeq("isobar"))
+ >>> p.nextn(16)
+ >>> [8, 18, 14, 1, 0, 17, 8, 18, 14, 1, 0, 17, 8, 18, 14, 1]
+ """
+
+ def __init__(self, list, item):
+ self.list = list
+ self.item = item
+
+ def next(self):
+ list = Pattern.value(self.list)
+ item = Pattern.value(self.item)
+ if list is None or item is None or item not in list:
+ return None
+ return list.index(item)
+
class PPad(Pattern):
- """ PPad: Pad with rests until it reaches length .
- """
- def __init__(self, pattern, length):
- self.pattern = pattern
- self.length = length
- self.count = 0
+ """ PPad: Pad with rests until it reaches length .
+ """
- def next(self):
- try:
- rv = self.pattern.next()
- except:
- if self.count >= self.length:
- raise StopIteration
- rv = None
+ def __init__(self, pattern, length):
+ self.pattern = pattern
+ self.length = length
+ self.count = 0
+
+ def next(self):
+ try:
+ rv = self.pattern.next()
+ except:
+ if self.count >= self.length:
+ raise StopIteration
+ rv = None
+
+ self.count += 1
+ return rv
- self.count += 1
- return rv
class PPadToMultiple(Pattern):
- """ PPadToMultiple: Pad with rests until its length is divisible by .
- Enforces a minimum padding of .
-
- Useful to create patterns which occupy a whole number of bars.
- """
-
- def __init__(self, pattern, multiple, minimum_pad = 0):
- self.pattern = pattern
- self.multiple = multiple
- self.minimum_pad = minimum_pad
- self.count = 0
- self.padcount = 0
- self.terminated = False
-
- def next(self):
- try:
- rv = self.pattern.next()
- except:
- if self.padcount >= self.minimum_pad and (self.count % self.multiple == 0):
- raise StopIteration
- else:
- rv = None
- self.padcount += 1
-
- self.count += 1
- return rv
+
+ """ PPadToMultiple: Pad with rests until its length is divisible by .
+ Enforces a minimum padding of .
+
+ Useful to create patterns which occupy a whole number of bars.
+ """
+
+ def __init__(self, pattern, multiple, minimum_pad=0):
+ self.pattern = pattern
+ self.multiple = multiple
+ self.minimum_pad = minimum_pad
+ self.count = 0
+ self.padcount = 0
+ self.terminated = False
+
+ def next(self):
+ try:
+ rv = self.pattern.next()
+ except:
+ if self.padcount >= self.minimum_pad and (
+ self.count %
+ self.multiple == 0):
+ raise StopIteration
+ else:
+ rv = None
+ self.padcount += 1
+
+ self.count += 1
+ return rv
diff --git a/isobar/pattern/oscillator.py b/isobar/pattern/oscillator.py
index 4cb9349..12ed4e8 100644
--- a/isobar/pattern/oscillator.py
+++ b/isobar/pattern/oscillator.py
@@ -6,33 +6,34 @@
from isobar.pattern.core import *
+
class PTri(Pattern):
- """ PTri: Generates a triangle waveform of period .
- >>> p = PTri(10)
- >>> p.nextn(10)
- [0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 0.8, 0.6, 0.4, 0.2]
- """
+ """ PTri: Generates a triangle waveform of period .
- def __init__(self, length = 10):
- self.length = length
- self.reset()
+ >>> p = PTri(10)
+ >>> p.nextn(10)
+ [0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 0.8, 0.6, 0.4, 0.2]
+ """
- def reset(self):
- self.phase = 0.0
+ def __init__(self, length=10):
+ self.length = length
+ self.reset()
- def next(self):
- length = Pattern.value(self.length)
+ def reset(self):
+ self.phase = 0.0
- norm_phase = float(self.phase) / length
- if norm_phase < 0.5:
- rv = norm_phase * 2.0
- else:
- rv = 1.0 - (norm_phase - 0.5) * 2.0
+ def next(self):
+ length = Pattern.value(self.length)
- self.phase += 1
- if self.phase > length:
- self.phase -= length
+ norm_phase = float(self.phase) / length
+ if norm_phase < 0.5:
+ rv = norm_phase * 2.0
+ else:
+ rv = 1.0 - (norm_phase - 0.5) * 2.0
- return rv
+ self.phase += 1
+ if self.phase > length:
+ self.phase -= length
+ return rv
diff --git a/isobar/pattern/sequence.py b/isobar/pattern/sequence.py
index 3dafae0..6e5612a 100644
--- a/isobar/pattern/sequence.py
+++ b/isobar/pattern/sequence.py
@@ -9,669 +9,726 @@
from isobar.key import *
from isobar.util import *
from isobar.chord import *
+from functools import reduce
+
class PSeq(Pattern):
- """ PSeq: Sequence of values based on an array
- Takes an input list, and repeats the items in this list.
- >>> p = PSeq([ 1, 2, 3, 5 ])
- >>> p.nextn(10)
- [1, 2, 3, 5, 1, 2, 3, 5, 1, 2, 3, 5, 1, 2, 3, 5]
- """
+ """ PSeq: Sequence of values based on an array
+ Takes an input list, and repeats the items in this list.
+
+ >>> p = PSeq([ 1, 2, 3, 5 ])
+ >>> p.nextn(10)
+ [1, 2, 3, 5, 1, 2, 3, 5, 1, 2, 3, 5, 1, 2, 3, 5]
+ """
+
+ def __init__(self, list=[], repeats=sys.maxsize):
+ #----------------------------------------------------------------------
+ # take a copy of the list to avoid changing the original
+ #----------------------------------------------------------------------
+ assert hasattr(list, "__getitem__"), "PSeq must take a list argument"
+ self.list = copy.copy(list)
+ self.repeats = repeats
- def __init__(self, list = [], repeats = sys.maxint):
- #------------------------------------------------------------------------
- # take a copy of the list to avoid changing the original
- #------------------------------------------------------------------------
- assert hasattr(list, "__getitem__"), "PSeq must take a list argument"
- self.list = copy.copy(list)
- self.repeats = repeats
+ self.reset()
- self.reset()
+ def reset(self):
+ self.rcount = 0
+ self.pos = 0
- def reset(self):
- self.rcount = 0
- self.pos = 0
+ def next(self):
+ if len(self.list) == 0 or self.rcount >= self.repeats:
+ raise StopIteration
- def next(self):
- if len(self.list) == 0 or self.rcount >= self.repeats:
- raise StopIteration
+ # support for pattern arguments
+ list = self.value(self.list)
+ repeats = self.value(self.repeats)
- # support for pattern arguments
- list = self.value(self.list)
- repeats = self.value(self.repeats)
+ rv = Pattern.value(list[self.pos])
+ self.pos += 1
+ if self.pos >= len(list):
+ self.pos = 0
+ self.rcount += 1
- rv = Pattern.value(list[self.pos])
- self.pos += 1
- if self.pos >= len(list):
- self.pos = 0
- self.rcount += 1
+ return rv
- return rv
class PSeries(Pattern):
- """ PSeries: Arithmetic series, beginning at , increment by
-
- >>> p = PSeries(3, 9)
- >>> p.nextn(16)
- [3, 12, 21, 30, 39, 48, 57, 66, 75, 84, 93, 102, 111, 120, 129, 138]
- """
-
- def __init__(self, start = 0, step = 1, length = sys.maxint):
- self.start = start
- self.value = start
- self.step = step
- self.length = length
- self.count = 0
-
- def reset(self):
- self.value = self.start
- self.count = 0
-
- Pattern.reset(self)
-
- def next(self):
- if self.count >= self.length:
- # return None
- raise StopIteration
- step = Pattern.value(self.step)
- n = self.value
- self.value += step
- self.count += 1
- return n
+
+ """ PSeries: Arithmetic series, beginning at , increment by
+
+ >>> p = PSeries(3, 9)
+ >>> p.nextn(16)
+ [3, 12, 21, 30, 39, 48, 57, 66, 75, 84, 93, 102, 111, 120, 129, 138]
+ """
+
+ def __init__(self, start=0, step=1, length=sys.maxsize):
+ self.start = start
+ self.value = start
+ self.step = step
+ self.length = length
+ self.count = 0
+
+ def reset(self):
+ self.value = self.start
+ self.count = 0
+
+ Pattern.reset(self)
+
+ def next(self):
+ if self.count >= self.length:
+ # return None
+ raise StopIteration
+ step = Pattern.value(self.step)
+ n = self.value
+ self.value += step
+ self.count += 1
+ return n
+
class PRange(Pattern):
- """ PRange: Similar to PSeries, but specify a max/step value.
-
- >>> p = PRange(0, 20, 2)
- >>> p.nextn(16)
- [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
- """
-
- def __init__(self, start = 0, end = 128, step = 1):
- self.start = start
- self.end = end
- self.step = step
- self.reset()
-
- def reset(self):
- self.value = self.start
-
- Pattern.reset(self)
-
- def next(self):
- step = Pattern.value(self.step)
- if step > 0 and self.value >= self.end:
- raise StopIteration
- elif step < 0 and self.value <= self.end:
- raise StopIteration
- rv = self.value
- self.value += step
- return rv
+
+ """ PRange: Similar to PSeries, but specify a max/step value.
+
+ >>> p = PRange(0, 20, 2)
+ >>> p.nextn(16)
+ [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
+ """
+
+ def __init__(self, start=0, end=128, step=1):
+ self.start = start
+ self.end = end
+ self.step = step
+ self.reset()
+
+ def reset(self):
+ self.value = self.start
+
+ Pattern.reset(self)
+
+ def next(self):
+ step = Pattern.value(self.step)
+ if step > 0 and self.value >= self.end:
+ raise StopIteration
+ elif step < 0 and self.value <= self.end:
+ raise StopIteration
+ rv = self.value
+ self.value += step
+ return rv
+
class PGeom(Pattern):
- """ PGeom: Geometric series, beginning at , multiplied by
- >>> p = PGeom(1, 2)
- >>> p.nextn(16)
- [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768]
- """
+ """ PGeom: Geometric series, beginning at , multiplied by
+
+ >>> p = PGeom(1, 2)
+ >>> p.nextn(16)
+ [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768]
+ """
- def __init__(self, start = 1, multiply = 2, length = sys.maxint):
- self.start = start
- self.value = start
- self.multiply = multiply
- self.length = length
- self.count = 0
+ def __init__(self, start=1, multiply=2, length=sys.maxsize):
+ self.start = start
+ self.value = start
+ self.multiply = multiply
+ self.length = length
+ self.count = 0
- def reset(self):
- self.value = self.start
- self.count = 0
+ def reset(self):
+ self.value = self.start
+ self.count = 0
- Pattern.reset(self)
+ Pattern.reset(self)
- def next(self):
- if self.count >= self.length:
- raise StopIteration
+ def next(self):
+ if self.count >= self.length:
+ raise StopIteration
- multiply = Pattern.value(self.multiply)
+ multiply = Pattern.value(self.multiply)
+
+ rv = self.value
+ self.value *= multiply
+ self.count += 1
+ return rv
- rv = self.value
- self.value *= multiply
- self.count += 1
- return rv
class PLoop(Pattern):
- """ PLoop: Repeats a finite for repeats.
- Useful for pattern generators which don't natively loop.
-
- Input must be finite or results may vary.
-
- >>> p = PLoop(PSeq([ 1, 4, 9 ], 1))
- >>> p.nextn(16)
- [1, 4, 9, 1, 4, 9, 1, 4, 9, 1, 4, 9, 1, 4, 9, 1]
- """
-
- def __init__(self, pattern, count = sys.maxint, bang = False):
- self.pattern = pattern
- self.count = count
- self.bang = bang
- self.reset()
-
- def reset(self):
- self.pos = 0
- self.rpos = 1
- self.read_all = False
- self.values = []
-
- Pattern.reset(self)
-
- def next(self):
- # print "%d, %d, %d" % (self.rebang, self.pos, self.rpos)
- if self.bang and self.pos >= len(self.values) and self.rpos >= self.count:
- self.reset()
- self.pattern.bang()
-
- if not self.read_all:
- # print "reading all"
- try:
- rv = self.pattern.next()
- self.values.append(rv)
- except StopIteration:
- self.read_all = True
-
- if self.read_all and self.pos >= len(self.values):
- if self.rpos >= self.count:
- raise StopIteration
- else:
- self.rpos += 1
- self.pos = 0
-
- # print "pos = %d, value len = %d" % (self.pos, len(self.values))
- rv = self.values[self.pos]
- self.pos += 1
- return rv
+
+ """ PLoop: Repeats a finite for repeats.
+ Useful for pattern generators which don't natively loop.
+
+ Input must be finite or results may vary.
+
+ >>> p = PLoop(PSeq([ 1, 4, 9 ], 1))
+ >>> p.nextn(16)
+ [1, 4, 9, 1, 4, 9, 1, 4, 9, 1, 4, 9, 1, 4, 9, 1]
+ """
+
+ def __init__(self, pattern, count=sys.maxsize, bang=False):
+ self.pattern = pattern
+ self.count = count
+ self.bang = bang
+ self.reset()
+
+ def reset(self):
+ self.pos = 0
+ self.rpos = 1
+ self.read_all = False
+ self.values = []
+
+ Pattern.reset(self)
+
+ def next(self):
+ # print "%d, %d, %d" % (self.rebang, self.pos, self.rpos)
+ if self.bang and self.pos >= len(
+ self.values) and self.rpos >= self.count:
+ self.reset()
+ self.pattern.bang()
+
+ if not self.read_all:
+ # print "reading all"
+ try:
+ rv = self.pattern.next()
+ self.values.append(rv)
+ except StopIteration:
+ self.read_all = True
+
+ if self.read_all and self.pos >= len(self.values):
+ if self.rpos >= self.count:
+ raise StopIteration
+ else:
+ self.rpos += 1
+ self.pos = 0
+
+ # print "pos = %d, value len = %d" % (self.pos, len(self.values))
+ rv = self.values[self.pos]
+ self.pos += 1
+ return rv
+
class PConcat(Pattern):
- """ PConcat: Concatenate the output of multiple finite sequences
-
- >>> PConcat([ PSeq([ 1, 2, 3], 2), PSeq([ 9, 8, 7 ], 2) ]).nextn(16)
- [1, 4, 9, 4, 1, 4, 9, 4, 1, 4, 9, 4, 1, 4, 9, 4]
- """
-
- def __init__(self, inputs):
- self.inputs = inputs
- self.current = inputs.pop(0)
-
- def next(self):
- try:
- return self.current.next()
- except StopIteration:
- if len(self.inputs) > 0:
- self.current = self.inputs.pop(0)
- # can't just blindly return the first value of current
- # -- what if it is empty?
- return self.next()
- else:
- # no more sequences left, so just return.
- raise StopIteration
+
+ """ PConcat: Concatenate the output of multiple finite sequences
+
+ >>> PConcat([ PSeq([ 1, 2, 3], 2), PSeq([ 9, 8, 7 ], 2) ]).nextn(16)
+ [1, 4, 9, 4, 1, 4, 9, 4, 1, 4, 9, 4, 1, 4, 9, 4]
+ """
+
+ def __init__(self, inputs):
+ self.inputs = inputs
+ self.current = inputs.pop(0)
+
+ def next(self):
+ try:
+ return self.current.next()
+ except StopIteration:
+ if len(self.inputs) > 0:
+ self.current = self.inputs.pop(0)
+ # can't just blindly return the first value of current
+ # -- what if it is empty?
+ return self.next()
+ else:
+ # no more sequences left, so just return.
+ raise StopIteration
+
class PPingPong(Pattern):
- """ PPingPong: Ping-pong input pattern back and forth N times.
-
- >>> p = PPingPong(PSeq([ 1, 4, 9 ], 1), 10)
- >>> p.nextn(16)
- [1, 4, 9, 4, 1, 4, 9, 4, 1, 4, 9, 4, 1, 4, 9, 4]
- """
-
- def __init__(self, pattern, count = 1):
- self.pattern = pattern
- self.count = count
- self.reset()
-
- def reset(self):
- self.pattern.reset()
- self.values = self.pattern.all()
- self.pos = 0
- self.dir = 1
- self.rpos = 0
-
- def next(self):
- if self.pos == 0 and self.rpos >= self.count:
- raise StopIteration
-
- rv = self.values[self.pos]
- self.pos += self.dir
- if self.pos == len(self.values) - 1:
- self.dir = -1
- elif self.pos == 0:
- self.dir = 1
- self.rpos += 1
-
- return rv
+
+ """ PPingPong: Ping-pong input pattern back and forth N times.
+
+ >>> p = PPingPong(PSeq([ 1, 4, 9 ], 1), 10)
+ >>> p.nextn(16)
+ [1, 4, 9, 4, 1, 4, 9, 4, 1, 4, 9, 4, 1, 4, 9, 4]
+ """
+
+ def __init__(self, pattern, count=1):
+ self.pattern = pattern
+ self.count = count
+ self.reset()
+
+ def reset(self):
+ self.pattern.reset()
+ self.values = self.pattern.all()
+ self.pos = 0
+ self.dir = 1
+ self.rpos = 0
+
+ def next(self):
+ if self.pos == 0 and self.rpos >= self.count:
+ raise StopIteration
+
+ rv = self.values[self.pos]
+ self.pos += self.dir
+ if self.pos == len(self.values) - 1:
+ self.dir = -1
+ elif self.pos == 0:
+ self.dir = 1
+ self.rpos += 1
+
+ return rv
+
class PCreep(Pattern):
- """ PCreep: Loop -note segment, progressing notes after repeats.
-
- >>> p = PCreep(PSeries(), 3, 1, 2)
- >>> p.nextn(16)
- [0, 1, 2, 0, 1, 2, 1, 2, 3, 1, 2, 3, 2, 3, 4, 2]
- """
- def __init__(self, pattern, length = 4, creep = 1, count = 1, prob = 1):
- self.pattern = pattern
- self.length = length
- self.creep = creep
- self.count = count
- self.prob = prob
-
- self.buffer = []
- self.pos = 0
- self.rcount = 1
- while len(self.buffer) < length:
- self.buffer.append(pattern.next())
-
- def next(self):
- pos = Pattern.value(self.pos)
- length = Pattern.value(self.length)
- creep = Pattern.value(self.creep)
- count = Pattern.value(self.count)
- prob = Pattern.value(self.prob)
-
- while len(self.buffer) < length:
- self.buffer.append(self.pattern.next())
- while len(self.buffer) > length:
- self.buffer.pop(0)
-
- if self.pos >= len(self.buffer):
- repeat = random.uniform(0, 1) < prob
-
- if self.rcount >= count or not repeat:
- #------------------------------------------------------------------------
- # finished creeping, pull some more data from our buffer
- #------------------------------------------------------------------------
- for n in range(creep):
- self.buffer.pop(0)
- self.buffer.append(self.pattern.next())
- self.rcount = 1
- else:
- #------------------------------------------------------------------------
- # finished the Nth repeat but, still more repeats to do
- #------------------------------------------------------------------------
- self.rcount += 1
-
- #------------------------------------------------------------------------
- # reset to the start of our buffer
- #------------------------------------------------------------------------
- if not repeat:
- self.pos -= 1
- else:
- self.pos = 0
-
- self.pos += 1
- return self.buffer[self.pos - 1]
+
+ """ PCreep: Loop -note segment, progressing notes after repeats.
+
+ >>> p = PCreep(PSeries(), 3, 1, 2)
+ >>> p.nextn(16)
+ [0, 1, 2, 0, 1, 2, 1, 2, 3, 1, 2, 3, 2, 3, 4, 2]
+ """
+
+ def __init__(self, pattern, length=4, creep=1, count=1, prob=1):
+ self.pattern = pattern
+ self.length = length
+ self.creep = creep
+ self.count = count
+ self.prob = prob
+
+ self.buffer = []
+ self.pos = 0
+ self.rcount = 1
+ while len(self.buffer) < length:
+ self.buffer.append(pattern.next())
+
+ def next(self):
+ pos = Pattern.value(self.pos)
+ length = Pattern.value(self.length)
+ creep = Pattern.value(self.creep)
+ count = Pattern.value(self.count)
+ prob = Pattern.value(self.prob)
+
+ while len(self.buffer) < length:
+ self.buffer.append(self.pattern.next())
+ while len(self.buffer) > length:
+ self.buffer.pop(0)
+
+ if self.pos >= len(self.buffer):
+ repeat = random.uniform(0, 1) < prob
+
+ if self.rcount >= count or not repeat:
+ #--------------------------------------------------------------
+ # finished creeping, pull some more data from our buffer
+ #--------------------------------------------------------------
+ for n in range(creep):
+ self.buffer.pop(0)
+ self.buffer.append(self.pattern.next())
+ self.rcount = 1
+ else:
+ #--------------------------------------------------------------
+ # finished the Nth repeat but, still more repeats to do
+ #--------------------------------------------------------------
+ self.rcount += 1
+
+ #------------------------------------------------------------------
+ # reset to the start of our buffer
+ #------------------------------------------------------------------
+ if not repeat:
+ self.pos -= 1
+ else:
+ self.pos = 0
+
+ self.pos += 1
+ return self.buffer[self.pos - 1]
+
class PStutter(Pattern):
- """ PStutter: Play each note of times.
- Is really a more convenient way to do:
-
- PCreep(pattern, 1, 1, count)
-
- >>> p = PStutter(PSeries(), 2)
- >>> p.nextn(16)
- [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7]
- """
-
- def __init__(self, pattern, count = 2):
- self.pattern = pattern
- self.count = count
- self.count_current = Pattern.value(count)
- self.pos = self.count_current
- self.value = 0
-
- def next(self):
- if self.pos >= self.count_current:
- self.count_current = Pattern.value(self.count)
- self.value = self.pattern.next()
- self.pos = 0
- self.pos += 1
- return self.value
+
+ """ PStutter: Play each note of times.
+ Is really a more convenient way to do:
+
+ PCreep(pattern, 1, 1, count)
+
+ >>> p = PStutter(PSeries(), 2)
+ >>> p.nextn(16)
+ [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7]
+ """
+
+ def __init__(self, pattern, count=2):
+ self.pattern = pattern
+ self.count = count
+ self.count_current = Pattern.value(count)
+ self.pos = self.count_current
+ self.value = 0
+
+ def next(self):
+ if self.pos >= self.count_current:
+ self.count_current = Pattern.value(self.count)
+ self.value = self.pattern.next()
+ self.pos = 0
+ self.pos += 1
+ return self.value
+
class PWrap(Pattern):
- """ PWrap: Wrap input note values within , .
-
- >>> p = PWrap(PSeries(5, 3), 0, 10)
- >>> p.nextn(16)
- [5, 8, 1, 4, 7, 0, 3, 6, 9, 2, 5, 8, 1, 4, 7, 0]
- """
-
- def __init__(self, pattern, min = 40, max = 80):
- self.pattern = pattern
- self.min = min
- self.max = max
-
- def next(self):
- value = self.pattern.next()
- while value < self.min:
- value += self.max - self.min
- while value >= self.max:
- value -= self.max - self.min
- return value
+
+ """ PWrap: Wrap input note values within , .
+
+ >>> p = PWrap(PSeries(5, 3), 0, 10)
+ >>> p.nextn(16)
+ [5, 8, 1, 4, 7, 0, 3, 6, 9, 2, 5, 8, 1, 4, 7, 0]
+ """
+
+ def __init__(self, pattern, min=40, max=80):
+ self.pattern = pattern
+ self.min = min
+ self.max = max
+
+ def next(self):
+ value = self.pattern.next()
+ while value < self.min:
+ value += self.max - self.min
+ while value >= self.max:
+ value -= self.max - self.min
+ return value
+
class PPermut(Pattern):
- """ PPermut: Generate every permutation of input items.
-
- >>> p = PPermut(PSeq([ 1, 11, 111, 1111 ]), 4)
- >>> p.nextn(16)
- [1, 11, 111, 1111, 1, 11, 1111, 111, 1, 111, 11, 1111, 1, 111, 1111, 11]
- """
-
- def __init__(self, input, count = 8):
- self.input = input
- self.count = count
- self.pos = sys.maxint
- self.permindex = sys.maxint
- self.permutations = []
-
- def reset(self):
- self.pos = sys.maxint
- self.permindex = sys.maxint
- self.permutations = []
-
- Pattern.reset(self)
-
- def next(self):
- if self.permindex > len(self.permutations):
- n = 0
- values = []
- while n < self.count:
- try:
- v = self.input.next()
- except StopIteration:
- break
-
- values.append(v)
- n += 1
-
- self.permutations = list(itertools.permutations(values))
- self.permindex = 0
- self.pos = 0
- elif self.pos >= len(self.permutations[0]):
- self.permindex = self.permindex + 1
- self.pos = 0
-
- if self.permindex >= len(self.permutations):
- return None
-
- rv = self.permutations[self.permindex][self.pos]
- self.pos += 1
- return rv
+
+ """ PPermut: Generate every permutation of input items.
+
+ >>> p = PPermut(PSeq([ 1, 11, 111, 1111 ]), 4)
+ >>> p.nextn(16)
+ [1, 11, 111, 1111, 1, 11, 1111, 111, 1, 111, 11, 1111, 1, 111, 1111, 11]
+ """
+
+ def __init__(self, input, count=8):
+ self.input = input
+ self.count = count
+ self.pos = sys.maxsize
+ self.permindex = sys.maxsize
+ self.permutations = []
+
+ def reset(self):
+ self.pos = sys.maxsize
+ self.permindex = sys.maxsize
+ self.permutations = []
+
+ Pattern.reset(self)
+
+ def next(self):
+ if self.permindex > len(self.permutations):
+ n = 0
+ values = []
+ while n < self.count:
+ try:
+ v = self.input.next()
+ except StopIteration:
+ break
+
+ values.append(v)
+ n += 1
+
+ self.permutations = list(itertools.permutations(values))
+ self.permindex = 0
+ self.pos = 0
+ elif self.pos >= len(self.permutations[0]):
+ self.permindex = self.permindex + 1
+ self.pos = 0
+
+ if self.permindex >= len(self.permutations):
+ return None
+
+ rv = self.permutations[self.permindex][self.pos]
+ self.pos += 1
+ return rv
+
class PDegree(Pattern):
- """ PDegree: Map scale index to MIDI notes in .
- >>> p = PDegree(PSeries(0, 1), Scale.major)
- >>> p.nextn(16)
- [0, 2, 4, 5, 7, 9, 11, 12, 14, 16, 17, 19, 21, 23, 24, 26]
- """
- def __init__(self, degree, scale = Scale.major):
- self.degree = degree
- self.scale = scale
+ """ PDegree: Map scale index to MIDI notes in .
+
+ >>> p = PDegree(PSeries(0, 1), Scale.major)
+ >>> p.nextn(16)
+ [0, 2, 4, 5, 7, 9, 11, 12, 14, 16, 17, 19, 21, 23, 24, 26]
+ """
+
+ def __init__(self, degree, scale=Scale.major):
+ self.degree = degree
+ self.scale = scale
- def next(self):
- degree = Pattern.value(self.degree)
- scale = Pattern.value(self.scale)
- if degree is None:
- return None
+ def next(self):
+ degree = Pattern.value(self.degree)
+ scale = Pattern.value(self.scale)
+ if degree is None:
+ return None
+
+ return scale[degree]
- return scale[degree]
class PSubsequence(Pattern):
- """ PSubsequence: Returns a finite subsequence of an input pattern.
- >>> p = PSubsequence(PSer
- >>> p.nextn(16)
- [0, 2, 4, 5, 7, 9, 11, 12, 14, 16, 17, 19, 21, 23, 24, 26]
- """
- def __init__(self, pattern, offset, length):
- self.pattern = pattern
- self.offset = offset
- self.length = length
- self.pos = 0
- self.values = []
+ """ PSubsequence: Returns a finite subsequence of an input pattern.
+
+ >>> p = PSubsequence(PSer
+ >>> p.nextn(16)
+ [0, 2, 4, 5, 7, 9, 11, 12, 14, 16, 17, 19, 21, 23, 24, 26]
+ """
+
+ def __init__(self, pattern, offset, length):
+ self.pattern = pattern
+ self.offset = offset
+ self.length = length
+ self.pos = 0
+ self.values = []
- def reset(self):
- self.pos = 0
+ def reset(self):
+ self.pos = 0
- Pattern.reset(self)
+ Pattern.reset(self)
- def next(self):
- offset = Pattern.value(self.offset)
- length = Pattern.value(self.length)
+ def next(self):
+ offset = Pattern.value(self.offset)
+ length = Pattern.value(self.length)
- # print "length is %d, pos %d" % (length, self.pos)
- if self.pos >= length:
- raise StopIteration
+ # print "length is %d, pos %d" % (length, self.pos)
+ if self.pos >= length:
+ raise StopIteration
- while len(self.values) <= self.pos + offset:
- self.values.append(self.pattern.next())
+ while len(self.values) <= self.pos + offset:
+ self.values.append(self.pattern.next())
- rv = self.values[offset + self.pos]
- self.pos += 1
+ rv = self.values[offset + self.pos]
+ self.pos += 1
+
+ return rv
- return rv
class PImpulse(Pattern):
- """ PImpulse: Outputs a 1 every events, otherwise 0.
-
- >>> p = PImpulse(4)
- >>> p.nextn(16)
- [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]
- """
- def __init__(self, period):
- self.period = period
- self.pos = period
-
- def reset(self):
- self.pos = 0
-
- def next(self):
- period = self.value(self.period)
-
- if self.pos >= period - 1:
- rv = 1
- self.pos = 0
- else:
- rv = 0
- self.pos += 1
-
- return rv
+
+ """ PImpulse: Outputs a 1 every events, otherwise 0.
+
+ >>> p = PImpulse(4)
+ >>> p.nextn(16)
+ [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]
+ """
+
+ def __init__(self, period):
+ self.period = period
+ self.pos = period
+
+ def reset(self):
+ self.pos = 0
+
+ def next(self):
+ period = self.value(self.period)
+
+ if self.pos >= period - 1:
+ rv = 1
+ self.pos = 0
+ else:
+ rv = 0
+ self.pos += 1
+
+ return rv
+
class PReset(Pattern):
- """ PReset: Resets each time it receives a zero-crossing from
-
-
- >>> p = PReset(PSeries(0, 1), PImpulse(4))
- >>> p.nextn(16)
- [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]
- """
- def __init__(self, pattern, trigger):
- self.value = 0
- self.pattern = pattern
- self.trigger = trigger
-
- def reset(self):
- self.value = 0
-
- def next(self):
- value = self.trigger.next()
- if value > 0 and self.value <= 0:
- self.pattern.reset()
- self.value = value
- elif value <= 0 and self.value > 0:
- self.value = value
-
- return self.pattern.next()
-
+
+ """ PReset: Resets each time it receives a zero-crossing from
+
+
+ >>> p = PReset(PSeries(0, 1), PImpulse(4))
+ >>> p.nextn(16)
+ [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]
+ """
+
+ def __init__(self, pattern, trigger):
+ self.value = 0
+ self.pattern = pattern
+ self.trigger = trigger
+
+ def reset(self):
+ self.value = 0
+
+ def next(self):
+ value = self.trigger.next()
+ if value > 0 and self.value <= 0:
+ self.pattern.reset()
+ self.value = value
+ elif value <= 0 and self.value > 0:
+ self.value = value
+
+ return self.pattern.next()
+
+
class PCounter(Pattern):
- """ PCounter: Increments a counter by 1 for each zero-crossing in .
-
- >>> p = PCounter(PImpulse(4))
- >>> p.nextn(16)
- [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4]
- """
- def __init__(self, trigger):
- self.trigger = trigger
- self.value = 0
- self.count = 0
-
- def next(self):
- value = self.trigger.next()
- if value > 0 and self.value <= 0:
- self.count += 1
- self.value = value
- elif value <= 0 and self.value > 0:
- self.value = value
-
- return self.count
+
+ """ PCounter: Increments a counter by 1 for each zero-crossing in .
+
+ >>> p = PCounter(PImpulse(4))
+ >>> p.nextn(16)
+ [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4]
+ """
+
+ def __init__(self, trigger):
+ self.trigger = trigger
+ self.value = 0
+ self.count = 0
+
+ def next(self):
+ value = self.trigger.next()
+ if value > 0 and self.value <= 0:
+ self.count += 1
+ self.value = value
+ elif value <= 0 and self.value > 0:
+ self.value = value
+
+ return self.count
+
class PArp(Pattern):
- """ PArp: Arpeggiator.
-
- can be one of:
- PArp.UP
- PArp.DOWN
- PArp.UPDOWN
- PArp.CONVERGE
- PArp.DIVERGE
- PArp.RANDOM
-
- >>> p = PLoop(PArp(Chord.major, PArp.CONVERGE))
- >>> p.nextn(16)
- [0, 12, 4, 7, 0, 12, 4, 7, 0, 12, 4, 7, 0, 12, 4, 7]
- """
- UP = 0
- DOWN = 1
- CONVERGE = 2
- DIVERGE = 2
- RANDOM = 3
-
- def __init__(self, chord = Chord.major, type = UP):
- self.chord = chord
- self.type = type
- self.pos = 0
- self.offsets = []
-
- try:
- #------------------------------------------------------------------------
- # prefer to specify a chord (or Key)
- #------------------------------------------------------------------------
- self.notes = self.chord.semitones
- except:
- #------------------------------------------------------------------------
- # can alternatively specify a list of notes
- #------------------------------------------------------------------------
- self.notes = self.chord
-
- if type == PArp.UP:
- self.offsets = range(len(self.notes))
- elif type == PArp.DOWN:
- self.offsets = list(reversed(range(len(self.notes))))
- elif type == PArp.CONVERGE:
- self.offsets = [ (n / 2) if (n % 2 == 0) else (0 - (n + 1) / 2) for n in xrange(len(self.notes)) ]
- elif type == PArp.DIVERGE:
- self.offsets = [ (n / 2) if (n % 2 == 0) else (0 - (n + 1) / 2) for n in xrange(len(self.notes)) ]
- self.offsets = list(reversed(self.offsets))
- elif type == PArp.RANDOM:
- self.offsets = range(len(self.notes))
- random.shuffle(self.offsets)
-
- def next(self):
- type = self.value(self.type)
- pos = self.value(self.pos)
-
- if pos < len(self.offsets):
- offset = self.offsets[pos]
- rv = self.notes[offset]
- self.pos = pos + 1
- return rv
- else:
- raise StopIteration
+
+ """ PArp: Arpeggiator.
+
+ can be one of:
+ PArp.UP
+ PArp.DOWN
+ PArp.UPDOWN
+ PArp.CONVERGE
+ PArp.DIVERGE
+ PArp.RANDOM
+
+ >>> p = PLoop(PArp(Chord.major, PArp.CONVERGE))
+ >>> p.nextn(16)
+ [0, 12, 4, 7, 0, 12, 4, 7, 0, 12, 4, 7, 0, 12, 4, 7]
+ """
+ UP = 0
+ DOWN = 1
+ CONVERGE = 2
+ DIVERGE = 2
+ RANDOM = 3
+
+ def __init__(self, chord=Chord.major, type=UP):
+ self.chord = chord
+ self.type = type
+ self.pos = 0
+ self.offsets = []
+
+ try:
+ #------------------------------------------------------------------
+ # prefer to specify a chord (or Key)
+ #------------------------------------------------------------------
+ self.notes = self.chord.semitones
+ except:
+ #------------------------------------------------------------------
+ # can alternatively specify a list of notes
+ #------------------------------------------------------------------
+ self.notes = self.chord
+
+ if type == PArp.UP:
+ self.offsets = range(len(self.notes))
+ elif type == PArp.DOWN:
+ self.offsets = list(reversed(range(len(self.notes))))
+ elif type == PArp.CONVERGE:
+ self.offsets = [(n /
+ 2) if (n %
+ 2 == 0) else (0 -
+ (n +
+ 1) /
+ 2) for n in xrange(len(self.notes))]
+ elif type == PArp.DIVERGE:
+ self.offsets = [(n /
+ 2) if (n %
+ 2 == 0) else (0 -
+ (n +
+ 1) /
+ 2) for n in xrange(len(self.notes))]
+ self.offsets = list(reversed(self.offsets))
+ elif type == PArp.RANDOM:
+ self.offsets = range(len(self.notes))
+ random.shuffle(self.offsets)
+
+ def next(self):
+ type = self.value(self.type)
+ pos = self.value(self.pos)
+
+ if pos < len(self.offsets):
+ offset = self.offsets[pos]
+ rv = self.notes[offset]
+ self.pos = pos + 1
+ return rv
+ else:
+ raise StopIteration
+
class PEuclidean(Pattern):
- """ PEuclidean: Generate Euclidean rhythms.
- Effectively tries to space events out evenly over beats.
- Events returned are either 1 or None (rest)
-
- >>> p = PEuclidean(8, 5)
- >>> p.nextn(8)
- [1, None, 1, 1, None, 1, 1, None]
- """
- def __init__(self, length, mod, phase = 0):
- self.length = length
- self.mod = mod
- self.sequence = []
- self.pos = phase
-
- def next(self):
- length = self.value(self.length)
- mod = self.value(self.mod)
- sequence = self.euclidean(length, mod)
- if self.pos >= len(sequence):
- self.pos = 0
- rv = sequence[self.pos]
- self.pos += 1
- return rv
-
- def split_remainder(self, seq):
- last = None
- a = []
- b = []
- for value in seq:
- if last is None or value == last:
- a.append(value)
- last = value
- else:
- b.append(value)
- return (a, b)
-
- def interleave(self, a, b):
- if len(a) < len(b):
- return [ a[n] + b[n] for n in range(len(a)) ] + b[len(a) - len(b):]
- elif len(b) < len(a):
- return [ a[n] + b[n] for n in range(len(b)) ] + a[len(b) - len(a):]
- else:
- return [ a[n] + b[n] for n in range(len(b)) ]
-
- def euclidean(self, length, mod):
- seqs = [ (1,) ] * mod + [ (None,) ] * (length - mod)
- seqs, remainder = self.split_remainder(seqs)
- while True:
- if len(remainder) <= 1:
- break
- seqs = self.interleave(seqs, remainder)
- seqs, remainder = self.split_remainder(seqs)
-
- return reduce(lambda a, b: a + b, seqs + remainder)
+
+ """ PEuclidean: Generate Euclidean rhythms.
+ Effectively tries to space events out evenly over beats.
+ Events returned are either 1 or None (rest)
+
+ >>> p = PEuclidean(8, 5)
+ >>> p.nextn(8)
+ [1, None, 1, 1, None, 1, 1, None]
+ """
+
+ def __init__(self, length, mod, phase=0):
+ self.length = length
+ self.mod = mod
+ self.sequence = []
+ self.pos = phase
+
+ def next(self):
+ length = self.value(self.length)
+ mod = self.value(self.mod)
+ sequence = self.euclidean(length, mod)
+ if self.pos >= len(sequence):
+ self.pos = 0
+ rv = sequence[self.pos]
+ self.pos += 1
+ return rv
+
+ def split_remainder(self, seq):
+ last = None
+ a = []
+ b = []
+ for value in seq:
+ if last is None or value == last:
+ a.append(value)
+ last = value
+ else:
+ b.append(value)
+ return (a, b)
+
+ def interleave(self, a, b):
+ if len(a) < len(b):
+ return [a[n] + b[n] for n in range(len(a))] + b[len(a) - len(b):]
+ elif len(b) < len(a):
+ return [a[n] + b[n] for n in range(len(b))] + a[len(b) - len(a):]
+ else:
+ return [a[n] + b[n] for n in range(len(b))]
+
+ def euclidean(self, length, mod):
+ seqs = [(1,)] * mod + [(None,)] * (length - mod)
+ seqs, remainder = self.split_remainder(seqs)
+ while True:
+ if len(remainder) <= 1:
+ break
+ seqs = self.interleave(seqs, remainder)
+ seqs, remainder = self.split_remainder(seqs)
+
+ return reduce(lambda a, b: a + b, seqs + remainder)
+
class PDecisionPoint(Pattern):
- """ PDecisionPoint: Each time its pattern is exhausted, requests a new pattern by calling .
-
- >>>
- >>>
- """
- def __init__(self, fn):
- self.fn = fn
- self.pattern = self.fn()
-
- def next(self):
- try:
- return self.pattern.next()
- except StopIteration:
- self.pattern = self.fn()
- # if not self.pattern:
- # causes this to break horribly in the PStaticSeq case
- # -- seems to try to evaluate self.pattern as a list
- if self.pattern is None:
- return None
- return self.next()
+ """ PDecisionPoint: Each time its pattern is exhausted, requests a new pattern by calling .
+
+ >>>
+ >>>
+ """
+
+ def __init__(self, fn):
+ self.fn = fn
+ self.pattern = self.fn()
+
+ def next(self):
+ try:
+ return self.pattern.next()
+ except StopIteration:
+ self.pattern = self.fn()
+ # if not self.pattern:
+ # causes this to break horribly in the PStaticSeq case
+ # -- seems to try to evaluate self.pattern as a list
+ if self.pattern is None:
+ return None
+ return self.next()
diff --git a/isobar/pattern/static.py b/isobar/pattern/static.py
index 720ecd9..a85b4bd 100644
--- a/isobar/pattern/static.py
+++ b/isobar/pattern/static.py
@@ -3,132 +3,139 @@
import inspect
import math
+
class PStaticViaOSC (Pattern):
- initialised = False
-
- def __init__(self, default = 0, address = "/value", port = 9900):
- if not PStaticViaOSC.initialised:
- import osc
+ initialised = False
+
+ def __init__(self, default=0, address="/value", port=9900):
+ if not PStaticViaOSC.initialised:
+ import osc
+
+ osc.init()
+ osc.listen(port=port)
- osc.init()
- osc.listen(port = port)
+ self.value = default
+ self.address = address
+ osc.bind(self.recv, address)
- self.value = default
- self.address = address
- osc.bind(self.recv, address)
+ def recv(self, msg, source=None):
+ address = msg[0]
+ signature = msg[1][1:]
+ print "(%s) %s" % (address, signature)
+ self.value = msg[2]
- def recv(self, msg, source = None):
- address = msg[0]
- signature = msg[1][1:]
- print "(%s) %s" % (address, signature)
- self.value = msg[2]
+ def next(self):
+ return self.value
- def next(self):
- return self.value
class PStaticTimeline (Pattern):
- """ PStaticTimeline: Returns the position (in beats) of the current timeline. """
- def __init__(self, timeline = None):
- self.given_timeline = timeline
+ """ PStaticTimeline: Returns the position (in beats) of the current timeline. """
+
+ def __init__(self, timeline=None):
+ self.given_timeline = timeline
+
+ def next(self):
+ beats = self.get_beats()
+ return round(beats, 5)
- def next(self):
- beats = self.get_beats()
- return round(beats, 5)
+ def get_beats(self):
+ #----------------------------------------------------------------------
+ # using the specified timeline (if given) or the currently-embedded
+ # timeline (otherwise), return the current position in beats.
+ #----------------------------------------------------------------------
+ timeline = self.given_timeline if self.given_timeline else self.timeline
+ if timeline:
+ return timeline.beats
- def get_beats(self):
- #------------------------------------------------------------------------
- # using the specified timeline (if given) or the currently-embedded
- # timeline (otherwise), return the current position in beats.
- #------------------------------------------------------------------------
- timeline = self.given_timeline if self.given_timeline else self.timeline
- if timeline:
- return timeline.beats
+ return 0
- return 0
class PStaticGlobal(Pattern):
- """ PStaticGlobal: Static global value identified by a string, with OSC listener """
- dict = {}
- listening = False
-
- def __init__(self, name, value = None):
- self.name = name
- if value is not None:
- PStaticGlobal.set(name, value)
-
- def next(self):
- name = Pattern.value(self.name)
- value = PStaticGlobal.dict[name]
- return Pattern.value(value)
-
- # BROKEN: not sure why
- @classmethod
- def _get(self, key):
- value = PStaticGlobal.dict[key]
- return Pattern.value(value)
-
- @classmethod
- def set(self, key, value):
- PStaticGlobal.dict[key] = value
-
- @classmethod
- def listen(self, prefix = "/global", port = 9900):
- if not PStaticGlobal.listening:
- import threading
- import OSC
-
- server = OSC.OSCServer(("localhost", port))
- server.addMsgHandler(prefix, self.recv)
- self.thread = threading.Thread(target = server.serve_forever)
- self.thread.setDaemon(True)
- self.thread.start()
-
- PStaticGlobal.listening = True
-
- self.prefix = prefix
-
- @classmethod
- def recv(self, addr, tags, data, client_address):
- # print "GOT MSG"
- key = data[0]
- value = data[1]
- print "(%s) %s = %.1f" % (addr, key, value)
- PStaticGlobal.set(key, value)
+
+ """ PStaticGlobal: Static global value identified by a string, with OSC listener """
+ dict = {}
+ listening = False
+
+ def __init__(self, name, value=None):
+ self.name = name
+ if value is not None:
+ PStaticGlobal.set(name, value)
+
+ def next(self):
+ name = Pattern.value(self.name)
+ value = PStaticGlobal.dict[name]
+ return Pattern.value(value)
+
+ # BROKEN: not sure why
+ @classmethod
+ def _get(self, key):
+ value = PStaticGlobal.dict[key]
+ return Pattern.value(value)
+
+ @classmethod
+ def set(self, key, value):
+ PStaticGlobal.dict[key] = value
+
+ @classmethod
+ def listen(self, prefix="/global", port=9900):
+ if not PStaticGlobal.listening:
+ import threading
+ import OSC
+
+ server = OSC.OSCServer(("localhost", port))
+ server.addMsgHandler(prefix, self.recv)
+ self.thread = threading.Thread(target=server.serve_forever)
+ self.thread.setDaemon(True)
+ self.thread.start()
+
+ PStaticGlobal.listening = True
+
+ self.prefix = prefix
+
+ @classmethod
+ def recv(self, addr, tags, data, client_address):
+ # print "GOT MSG"
+ key = data[0]
+ value = data[1]
+ print "(%s) %s = %.1f" % (addr, key, value)
+ PStaticGlobal.set(key, value)
+
class PStaticTimelineSine(PStaticTimeline):
- def __init__(self, period):
- PStaticTimeline.__init__(self)
- self.period = period
- def next(self):
- period = Pattern.value(self.period)
- # self.phase += math.pi * 2.0 / period
+ def __init__(self, period):
+ PStaticTimeline.__init__(self)
+ self.period = period
+
+ def next(self):
+ period = Pattern.value(self.period)
+ # self.phase += math.pi * 2.0 / period
- beats = self.get_beats()
- rv = math.sin(2 * math.pi * beats / self.period)
- return rv
+ beats = self.get_beats()
+ rv = math.sin(2 * math.pi * beats / self.period)
+ return rv
class PStaticSeq(Pattern):
- def __init__(self, sequence, duration):
- #------------------------------------------------------------------------
- # take a copy of the list to avoid changing the original
- #------------------------------------------------------------------------
- self.sequence = copy.copy(sequence)
- self.duration = duration
- self.start = None
-
- def next(self):
- timeline = self.timeline
- if self.start is None:
- self.start = round(timeline.beats, 5)
-
- now = round(timeline.beats, 5)
- if now - self.start >= self.duration:
- self.sequence.pop(0)
- self.start = now
- if len(self.sequence) == 0:
- raise StopIteration
- return self.sequence[0]
+ def __init__(self, sequence, duration):
+ #----------------------------------------------------------------------
+ # take a copy of the list to avoid changing the original
+ #----------------------------------------------------------------------
+ self.sequence = copy.copy(sequence)
+ self.duration = duration
+ self.start = None
+
+ def next(self):
+ timeline = self.timeline
+ if self.start is None:
+ self.start = round(timeline.beats, 5)
+
+ now = round(timeline.beats, 5)
+ if now - self.start >= self.duration:
+ self.sequence.pop(0)
+ self.start = now
+ if len(self.sequence) == 0:
+ raise StopIteration
+ return self.sequence[0]
diff --git a/isobar/pattern/warp.py b/isobar/pattern/warp.py
index e1a5e3c..519240d 100644
--- a/isobar/pattern/warp.py
+++ b/isobar/pattern/warp.py
@@ -3,104 +3,113 @@
import math
+
class PWarp(Pattern):
- pass
+ pass
+
class PWInterpolate(PWarp):
- """ PWInterpolate: Requests a new target warp value from every beats
- and applies linear interpolation to ramp between values.
- To select a new target warp value every 8 beats, between [-0.5, 0.5]:
+ """ PWInterpolate: Requests a new target warp value from every beats
+ and applies linear interpolation to ramp between values.
+
+ To select a new target warp value every 8 beats, between [-0.5, 0.5]:
+
+ >>> p = PWInterpolate(PWhite(-0.5, 0.5), 8)
+ """
- >>> p = PWInterpolate(PWhite(-0.5, 0.5), 8)
- """
- def __init__(self, pattern, length = 1):
- self.length = length
- self.pattern = pattern
- self.pos = self.length
- self.value = 0.0
+ def __init__(self, pattern, length=1):
+ self.length = length
+ self.pattern = pattern
+ self.pos = self.length
+ self.value = 0.0
- def next(self):
- rv = self.value
+ def next(self):
+ rv = self.value
- #------------------------------------------------------------------------
- # keep ticking until we have reached our period (length, in beats)
- #------------------------------------------------------------------------
- self.pos += 1.0 / TICKS_PER_BEAT
- if self.pos >= self.length:
- self.pos = 0
- self.target = self.pattern.next()
+ #----------------------------------------------------------------------
+ # keep ticking until we have reached our period (length, in beats)
+ #----------------------------------------------------------------------
+ self.pos += 1.0 / TICKS_PER_BEAT
+ if self.pos >= self.length:
+ self.pos = 0
+ self.target = self.pattern.next()
- #------------------------------------------------------------------------
- # in case our length parameter is also a pattern: obtain a scalar value.
- # dv is used for linear interpolation until the next target reached.
- #------------------------------------------------------------------------
- length = Pattern.value(self.length)
- self.dv = (self.target - self.value) / (TICKS_PER_BEAT * length)
+ #------------------------------------------------------------------
+ # in case our length parameter is also a pattern: obtain a scalar value.
+ # dv is used for linear interpolation until the next target reached.
+ #------------------------------------------------------------------
+ length = Pattern.value(self.length)
+ self.dv = (self.target - self.value) / (TICKS_PER_BEAT * length)
- self.value = self.value + self.dv
+ self.value = self.value + self.dv
+
+ return rv
- return rv
class PWSine(PWarp):
- """ PWSine: Sinosoidal warp, period beats, amplitude +/-.
- >>> p = PWAmp(8, 0.5)
- """
- def __init__(self, length = 1, amp = 0.5):
- self.length = length
- self.amp = amp
- self.pos = 0.0
+ """ PWSine: Sinosoidal warp, period beats, amplitude +/-.
+
+ >>> p = PWAmp(8, 0.5)
+ """
+
+ def __init__(self, length=1, amp=0.5):
+ self.length = length
+ self.amp = amp
+ self.pos = 0.0
+
+ def next(self):
+ self.pos += 1.0 / TICKS_PER_BEAT
+ if self.pos > self.length:
+ self.pos -= self.length
- def next(self):
- self.pos += 1.0 / TICKS_PER_BEAT
- if self.pos > self.length:
- self.pos -= self.length
+ # normalize to [0, 1]
+ pos_norm = self.pos / self.length
+ warp = math.sin(2.0 * math.pi * pos_norm)
+ warp = warp * self.amp
- # normalize to [0, 1]
- pos_norm = self.pos / self.length
- warp = math.sin(2.0 * math.pi * pos_norm)
- warp = warp * self.amp
+ return warp
- return warp
class PWRallantando(PWarp):
- """ PWRallantando: Exponential deceleration to times the current tempo over beats.
-
- >>> p = PWRallantando(8, 0.5)
- """
- def __init__(self, length = 1, amp = 0.5):
- self.length = length
- self.amp = amp
- self.pos = 0.0
- self.value = 1.0
- self.length_cur = -1.0
- self.dv = None
-
- def next(self):
- rv = self.value
-
- #------------------------------------------------------------------------
- # need to round or we might succumb to the dreaded python rounding
- # error (eg 0.99999 < 0 when multiplying 1/24.0 by 24)
- #------------------------------------------------------------------------
- if round(self.pos, 8) >= round(self.length_cur, 8):
- self.value = 1.0
- rv = 1.0
- self.pos = 0
- self.length_cur = Pattern.value(self.length)
- amp_cur = Pattern.value(self.amp)
- rate_start = 1.0
- rate_end = 1.0 + amp_cur
- steps = TICKS_PER_BEAT * self.length_cur
- self.dv = math.exp(math.log(rate_end / rate_start) / steps)
-
- self.pos += 1.0 / TICKS_PER_BEAT
- self.value = self.value * self.dv
- #------------------------------------------------------------------------
- # subtract
- #------------------------------------------------------------------------
- rv = math.log(rv, 2)
- print "warp: %f" % rv
- return rv
+ """ PWRallantando: Exponential deceleration to times the current tempo over beats.
+
+ >>> p = PWRallantando(8, 0.5)
+ """
+
+ def __init__(self, length=1, amp=0.5):
+ self.length = length
+ self.amp = amp
+ self.pos = 0.0
+ self.value = 1.0
+ self.length_cur = -1.0
+ self.dv = None
+
+ def next(self):
+ rv = self.value
+
+ #----------------------------------------------------------------------
+ # need to round or we might succumb to the dreaded python rounding
+ # error (eg 0.99999 < 0 when multiplying 1/24.0 by 24)
+ #----------------------------------------------------------------------
+ if round(self.pos, 8) >= round(self.length_cur, 8):
+ self.value = 1.0
+ rv = 1.0
+ self.pos = 0
+ self.length_cur = Pattern.value(self.length)
+ amp_cur = Pattern.value(self.amp)
+ rate_start = 1.0
+ rate_end = 1.0 + amp_cur
+ steps = TICKS_PER_BEAT * self.length_cur
+ self.dv = math.exp(math.log(rate_end / rate_start) / steps)
+
+ self.pos += 1.0 / TICKS_PER_BEAT
+ self.value = self.value * self.dv
+ #----------------------------------------------------------------------
+ # subtract
+ #----------------------------------------------------------------------
+ rv = math.log(rv, 2)
+ print "warp: %f" % rv
+ return rv
diff --git a/isobar/scale.py b/isobar/scale.py
index 1774b53..f96e25e 100644
--- a/isobar/scale.py
+++ b/isobar/scale.py
@@ -1,154 +1,191 @@
import random
from util import *
+
class Scale(object):
- dict = { }
-
- def __init__(self, semitones = [ 0, 2, 4, 5, 7, 9, 11 ], name = "unnamed scale", octave_size = 12):
- self.semitones = semitones
- """ For polymorphism with WeightedScale -- assume all notes equally weighted. """
- self.weights = [ 1.0 / len(self.semitones) for _ in range(len(self.semitones)) ]
- self.name = name
- self.octave_size = octave_size
- if not Scale.dict.has_key(name):
- Scale.dict[name] = self
-
- def __str__(self):
- return "%s %s" % (self.name, self.semitones)
-
- def __getitem__(self, key):
- return self.get(key)
-
- def __eq__(self, other):
- return self.semitones == other.semitones and self.octave_size == other.octave_size
-
- def __contains__(self, semitone):
- return (semitone % self.scale.octave_size) in self.semitones
-
- def get(self, n):
- """ Retrieve the n'th degree of this scale. """
- if n is None:
- return None
-
- octave = int(n / len(self.semitones))
- degree = n % len(self.semitones)
- note = (self.octave_size * octave) + self.semitones[degree]
- return note
-
- def copy(self):
- other = Scale(self.semitones, self.name)
- return other
-
- def change(self):
- """ Exchange two random elements of this scale. """
- i = random.randint(0, len(self.semitones) - 1)
- j = random.randint(0, len(self.semitones) - 1)
- if i <> j:
- tmp = self.semitones[i]
- self.semitones[i] = self.semitones[j]
- self.semitones[j] = tmp
- return self
-
- def shuffle(self):
- random.shuffle(self.semitones)
- return self
-
- def indexOf(self, note):
- """ Return the index of the given note within this scale. """
- octave = int(note / self.octave_size)
- index = octave * len(self.semitones)
- note -= octave * self.octave_size
- degree = 0
-
- while note > self.semitones[degree] and degree < len(self.semitones) - 1:
- degree += 1
-
- index += degree
- return index
-
- @staticmethod
- def fromnotes(notes, name = "unnamed scale", octave_size = 12):
- notes = [ note % octave_size for note in notes ]
- notes = dict((k, k) for k in notes).keys()
- notes = sorted(notes)
- scale = Scale(notes, name = name, octave_size = octave_size)
- return scale
-
- @staticmethod
- def all():
- return Scale.dict.values()
-
- @staticmethod
- def byname(name):
- return Scale.dict[name]
-
- @staticmethod
- def random():
- key = random.choice(Scale.dict.keys())
- return Scale.dict[key]
-
-Scale.chromatic = Scale([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ], "chromatic")
-Scale.major = Scale([ 0, 2, 4, 5, 7, 9, 11 ], "major")
-Scale.maj7 = Scale([ 0, 2, 4, 5, 7, 9, 10 ], "maj7") ## XXX?
-Scale.minor = Scale([ 0, 2, 3, 5, 7, 8, 11 ], "minor")
-Scale.pureminor = Scale([ 0, 3, 7 ], "pureminor")
-Scale.puremajor = Scale([ 0, 4, 7 ], "puremajor")
-Scale.minorPenta = Scale([ 0, 3, 5, 7, 10 ], "minorPenta")
-Scale.majorPenta = Scale([ 0, 2, 4, 7, 9 ], "majorPenta")
-Scale.ritusen = Scale([ 0, 2, 5, 7, 9 ], "ritusen")
-Scale.pelog = Scale([ 0, 1, 3, 7, 8 ], "pelog")
-Scale.augmented = Scale([ 0, 3, 4, 7, 8, 11 ], "augmented")
-Scale.augmented2 = Scale([ 0, 1, 4, 5, 8, 9 ], "augmented 2")
-Scale.wholetone = Scale([ 0, 2, 4, 6, 8, 10 ], "wholetone")
-
-Scale.ionian = Scale([ 0, 2, 4, 5, 7, 9, 11 ], "ionian")
-Scale.dorian = Scale([ 0, 2, 3, 5, 7, 9, 10 ], "dorian")
-Scale.phrygian = Scale([ 0, 1, 3, 5, 7, 8, 10 ], "phrygian")
-Scale.lydian = Scale([ 0, 2, 4, 6, 7, 9, 11 ], "lydian")
-Scale.mixolydian = Scale([ 0, 2, 4, 5, 7, 9, 10 ], "mixolydian")
-Scale.aeolian = Scale([ 0, 2, 3, 5, 7, 8, 10 ], "aeolian")
-Scale.locrian = Scale([ 0, 1, 3, 5, 6, 8, 10 ], "locrian")
-Scale.fourths = Scale([ 0, 2, 5, 7 ], "fourths")
-
-#------------------------------------------------------------------------
-# Use major scale as a global default. This can be overriden by user.
-#------------------------------------------------------------------------
-Scale.default = Scale.major
+ dict = {}
+
+ def __init__(
+ self,
+ semitones=[
+ 0,
+ 2,
+ 4,
+ 5,
+ 7,
+ 9,
+ 11],
+ name="unnamed scale",
+ octave_size=12):
+ self.semitones = semitones
+ """ For polymorphism with WeightedScale -- assume all notes equally weighted. """
+ self.weights = [1.0 / len(self.semitones)
+ for _ in range(len(self.semitones))]
+ self.name = name
+ self.octave_size = octave_size
+ if name not in Scale.dict:
+ Scale.dict[name] = self
+
+ def __str__(self):
+ return "%s %s" % (self.name, self.semitones)
+
+ def __getitem__(self, key):
+ return self.get(key)
+
+ def __eq__(self, other):
+ return self.semitones == other.semitones and self.octave_size == other.octave_size
+
+ def __contains__(self, semitone):
+ return (semitone % self.scale.octave_size) in self.semitones
+
+ def get(self, n):
+ """ Retrieve the n'th degree of this scale. """
+ if n is None:
+ return None
+
+ octave = int(n / len(self.semitones))
+ degree = n % len(self.semitones)
+ note = (self.octave_size * octave) + self.semitones[degree]
+ return note
+
+ def copy(self):
+ other = Scale(self.semitones, self.name)
+ return other
+
+ def change(self):
+ """ Exchange two random elements of this scale. """
+ i = random.randint(0, len(self.semitones) - 1)
+ j = random.randint(0, len(self.semitones) - 1)
+ if i != j:
+ tmp = self.semitones[i]
+ self.semitones[i] = self.semitones[j]
+ self.semitones[j] = tmp
+ return self
+
+ def shuffle(self):
+ random.shuffle(self.semitones)
+ return self
+
+ def indexOf(self, note):
+ """ Return the index of the given note within this scale. """
+ octave = int(note / self.octave_size)
+ index = octave * len(self.semitones)
+ note -= octave * self.octave_size
+ degree = 0
+
+ while note > self.semitones[
+ degree] and degree < len(self.semitones) - 1:
+ degree += 1
+
+ index += degree
+ return index
+
+ @staticmethod
+ def fromnotes(notes, name="unnamed scale", octave_size=12):
+ notes = [note % octave_size for note in notes]
+ notes = dict((k, k) for k in notes).keys()
+ notes = sorted(notes)
+ scale = Scale(notes, name=name, octave_size=octave_size)
+ return scale
+
+ @staticmethod
+ def all():
+ return Scale.dict.values()
+
+ @staticmethod
+ def byname(name):
+ return Scale.dict[name]
+
+ @staticmethod
+ def random():
+ key = random.choice(Scale.dict.keys())
+ return Scale.dict[key]
+
+Scale.chromatic = Scale([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], "chromatic")
+Scale.major = Scale([0, 2, 4, 5, 7, 9, 11], "major")
+Scale.maj7 = Scale([0, 2, 4, 5, 7, 9, 10], "maj7") # XXX?
+Scale.minor = Scale([0, 2, 3, 5, 7, 8, 11], "minor")
+Scale.pureminor = Scale([0, 3, 7], "pureminor")
+Scale.puremajor = Scale([0, 4, 7], "puremajor")
+Scale.minorPenta = Scale([0, 3, 5, 7, 10], "minorPenta")
+Scale.majorPenta = Scale([0, 2, 4, 7, 9], "majorPenta")
+Scale.ritusen = Scale([0, 2, 5, 7, 9], "ritusen")
+Scale.pelog = Scale([0, 1, 3, 7, 8], "pelog")
+Scale.augmented = Scale([0, 3, 4, 7, 8, 11], "augmented")
+Scale.augmented2 = Scale([0, 1, 4, 5, 8, 9], "augmented 2")
+Scale.wholetone = Scale([0, 2, 4, 6, 8, 10], "wholetone")
+
+Scale.ionian = Scale([0, 2, 4, 5, 7, 9, 11], "ionian")
+Scale.dorian = Scale([0, 2, 3, 5, 7, 9, 10], "dorian")
+Scale.phrygian = Scale([0, 1, 3, 5, 7, 8, 10], "phrygian")
+Scale.lydian = Scale([0, 2, 4, 6, 7, 9, 11], "lydian")
+Scale.mixolydian = Scale([0, 2, 4, 5, 7, 9, 10], "mixolydian")
+Scale.aeolian = Scale([0, 2, 3, 5, 7, 8, 10], "aeolian")
+Scale.locrian = Scale([0, 1, 3, 5, 6, 8, 10], "locrian")
+Scale.fourths = Scale([0, 2, 5, 7], "fourths")
+
+'''
+Use major scale as a global default. This can be overriden by user.
+'''
+
+Scale.default = Scale.major
+
class WeightedScale(Scale):
- def __init__(self, semitones = [ 0, 2, 4, 5, 7, 9, 11 ], weights = [ 1/7.0 ] * 7, name = "major", octave_size = 12):
- Scale.__init__(self, semitones, name = name, octave_size = octave_size)
- self.weights = weights
- if not Scale.dict.has_key(name):
- Scale.dict[name] = self
-
- def __str__(self):
- return "%s %s weights = %s" % (self.name, self.semitones, self.weights)
-
- @staticmethod
- def fromnotes(notes, name = "unnamed scale", octave_size = 12):
- note_sequence = [ note % octave_size for note in notes ]
- notes_dict = {}
- for note in note_sequence:
- if not note in notes_dict:
- notes_dict[note] = 0
- notes_dict[note] += 1.0 / len(note_sequence)
-
- notes_unique = dict((k, k) for k in note_sequence).keys()
- notes_unique = sorted(notes_unique)
- weights = []
- for note in notes_unique:
- weights.append(notes_dict[note])
-
- scale = WeightedScale(notes_unique, weights, name = name, octave_size = octave_size)
- return scale
-
- @staticmethod
- def fromorder(notes, name = "unnamed scale", octave_size = 12):
- notes = [ note % octave_size for note in notes ]
- weights = [ len(notes) - n for n in range(len(notes)) ]
- weights = normalize(weights)
-
- scale = WeightedScale(notes, weights, name = name, octave_size = octave_size)
- return scale
-
+
+ def __init__(
+ self,
+ semitones=[
+ 0,
+ 2,
+ 4,
+ 5,
+ 7,
+ 9,
+ 11],
+ weights=[
+ 1 / 7.0] * 7,
+ name="major",
+ octave_size=12):
+ Scale.__init__(self, semitones, name=name, octave_size=octave_size)
+ self.weights = weights
+ if name not in Scale.dict:
+ Scale.dict[name] = self
+
+ def __str__(self):
+ return "%s %s weights = %s" % (self.name, self.semitones, self.weights)
+
+ @staticmethod
+ def fromnotes(notes, name="unnamed scale", octave_size=12):
+ note_sequence = [note % octave_size for note in notes]
+ notes_dict = {}
+ for note in note_sequence:
+ if note not in notes_dict:
+ notes_dict[note] = 0
+ notes_dict[note] += 1.0 / len(note_sequence)
+
+ notes_unique = dict((k, k) for k in note_sequence).keys()
+ notes_unique = sorted(notes_unique)
+ weights = []
+ for note in notes_unique:
+ weights.append(notes_dict[note])
+
+ scale = WeightedScale(
+ notes_unique,
+ weights,
+ name=name,
+ octave_size=octave_size)
+ return scale
+
+ @staticmethod
+ def fromorder(notes, name="unnamed scale", octave_size=12):
+ notes = [note % octave_size for note in notes]
+ weights = [len(notes) - n for n in range(len(notes))]
+ weights = normalize(weights)
+
+ scale = WeightedScale(
+ notes,
+ weights,
+ name=name,
+ octave_size=octave_size)
+ return scale
diff --git a/isobar/timeline.py b/isobar/timeline.py
index 583dc34..5571213 100644
--- a/isobar/timeline.py
+++ b/isobar/timeline.py
@@ -11,477 +11,522 @@
TICKS_PER_BEAT = 24
+
class Timeline(object):
- CLOCK_INTERNAL = 0
- CLOCK_EXTERNAL = 1
-
- def __init__(self, bpm = 120, device = None, debug = None):
- """expect to receive one tick per beat, generate events at 120bpm"""
- self.ticklen = 1.0 / TICKS_PER_BEAT
- self.beats = 0
- self.devices = [ device ] if device else []
- self.channels = []
- self.automators = []
- self.max_channels = 0
-
- self.bpm = None
- self.clock = None
- self.clocksource = None
- self.thread = None
-
- self.stop_when_done = False
-
- if debug is not None:
- isobar.debug = debug
-
- self.events = []
-
- if hasattr(bpm, "clocktarget"):
- bpm.clocktarget = self
- self.clocksource = bpm
- self.clockmode = self.CLOCK_EXTERNAL
- else:
- self.bpm = bpm
- #------------------------------------------------------------------------
- # Create clock with a tick-size of 1/24th of a beat.
- #------------------------------------------------------------------------
- self.clock = Clock(60.0 / (self.bpm * TICKS_PER_BEAT))
- self.clockmode = self.CLOCK_INTERNAL
-
- def tick(self):
- # if round(self.beats, 5) % 1 == 0:
- # print "tick (%d active channels, %d pending events)" % (len(self.channels), len(self.events))
-
- #------------------------------------------------------------------------
- # copy self.events because removing from it whilst using it = bad idea.
- # perform events before channels are executed because an event might
- # include scheduling a quantized channel, which should then be
- # immediately evaluated.
- #------------------------------------------------------------------------
- for event in self.events[:]:
- #------------------------------------------------------------------------
- # the only event we currently get in a Timeline are add_channel events
- # -- which have a raw function associated with them.
- #
- # round needed because we can sometimes end up with beats = 3.99999999...
- # http://docs.python.org/tutorial/floatingpoint.html
- #------------------------------------------------------------------------
- if round(event["time"], 8) <= round(self.beats, 8):
- event["fn"]()
- self.events.remove(event)
-
- #------------------------------------------------------------------------
- # some devices (ie, MidiFileOut) require being told to tick
- #------------------------------------------------------------------------
- for device in self.devices:
- device.tick(self.ticklen)
-
- #------------------------------------------------------------------------
- # copy self.channels because removing from it whilst using it = bad idea
- #------------------------------------------------------------------------
- for channel in self.channels[:]:
- channel.tick(self.ticklen)
- if channel.finished:
- self.channels.remove(channel)
-
- if self.stop_when_done and len(self.channels) == 0:
- raise StopIteration
-
- #------------------------------------------------------------------------
- # TODO: should automator and channel inherit from a common superclass?
- # one is continuous, one is discrete.
- #------------------------------------------------------------------------
- for automator in self.automators[:]:
- automator.tick(self.ticklen)
- if automator.finished:
- self.automators.remove(automator)
-
- self.beats += self.ticklen
-
- def dump(self):
- """ Output a summary of this Timeline object
- """
- print "Timeline (clock: %s)" % ("external" if self.clockmode == self.CLOCK_EXTERNAL else "%sbpm" % self.bpm)
-
- print " - %d devices" % len(self.devices)
- for device in self.devices:
- print " - %s" % device
-
- print " - %d channels" % len(self.channels)
- for channel in self.channels:
- print" - %s" % channel
-
- def reset_to_beat(self):
- self.beats = round(self.beats)
- for channel in self.channels:
- channel.reset_to_beat()
-
- def reset(self):
- # print "tl reset!"
- self.beats = -.0001
- # XXX probably shouldn't have to do this - should channels read the tl ticks value?
- for channel in self.channels:
- channel.reset()
-
- def background(self):
- """ Run this Timeline in a background thread. """
- self.thread = thread.start_new_thread(self.run, ())
-
- def run(self, high_priority = True):
- """ Create clock with 64th-beat granularity.
- By default, attempts to run as a high-priority thread
- (though requires being run as root to re-nice the process) """
- try:
- import os
- os.nice(-20)
- print "Timeline: Running as high-priority thread"
- except:
- pass
-
- try:
- #------------------------------------------------------------------------
- # Start the clock. This might internal (eg a Clock object, running on
- # an independent thread), or external (eg a MIDI clock).
- #------------------------------------------------------------------------
- if self.clockmode == self.CLOCK_INTERNAL:
- self.clock.run(self)
- else:
- self.clocksource.run()
-
- except StopIteration:
- #------------------------------------------------------------------------
- # This will be hit if every Pattern in a timeline is exhausted.
- #------------------------------------------------------------------------
- print "Timeline finished"
-
- except Exception, e:
- print " *** Exception in background Timeline thread: %s" % e
- traceback.print_exc(file = sys.stdout)
-
- def warp(self, warper):
- self.clock.warp(warper)
-
- def unwarp(self, warper):
- self.clock.warp(warper)
-
- def set_output(self, device):
- self.devices.clear()
- self.add_output(device)
-
- def add_output(self, device):
- self.devices.append(device)
-
- def sched(self, event, quantize = 0, delay = 0, count = 0, device = None):
- if not device:
- if not self.devices:
- isobar.log("Adding default MIDI output")
- self.add_output(isobar.io.MidiOut())
- device = self.devices[0]
-
- if self.max_channels and len(self.channels) >= self.max_channels:
- print "*** timeline: refusing to schedule channel (hit limit of %d)" % self.max_channels
- return
-
- # hmm - why do we need to copy this?
- # c = channel(copy.deepcopy(dict))
- # c = Channel(copy.copy(dict))
- def addchan():
- #----------------------------------------------------------------------
- # this isn't exactly the best way to determine whether a device is
- # an automator or event generator. should we have separate calls?
- #----------------------------------------------------------------------
- if type(event) == dict and event.has_key("control") and False:
- pass
- else:
- c = Channel(event, count)
- c.timeline = self
- c.device = device
- self.channels.append(c)
-
- if quantize or delay:
- if quantize:
- schedtime = quantize * math.ceil(float(self.beats + delay) / quantize)
- else:
- schedtime = self.beats + delay
- self.events.append({ 'time' : schedtime, 'fn' : addchan })
- else:
- addchan()
+ CLOCK_INTERNAL = 0
+ CLOCK_EXTERNAL = 1
+
+ def __init__(self, bpm=120, device=None, debug=None):
+ """expect to receive one tick per beat, generate events at 120bpm"""
+ self.ticklen = 1.0 / TICKS_PER_BEAT
+ self.beats = 0
+ self.devices = [device] if device else []
+ self.channels = []
+ self.automators = []
+ self.max_channels = 0
+
+ self.bpm = None
+ self.clock = None
+ self.clocksource = None
+ self.thread = None
+
+ self.stop_when_done = False
+
+ if debug is not None:
+ isobar.debug = debug
+
+ self.events = []
+
+ if hasattr(bpm, "clocktarget"):
+ bpm.clocktarget = self
+ self.clocksource = bpm
+ self.clockmode = self.CLOCK_EXTERNAL
+ else:
+ self.bpm = bpm
+ '''
+ # Create clock with a tick-size of 1/24th of a beat.
+ '''
+ self.clock = Clock(60.0 / (self.bpm * TICKS_PER_BEAT))
+ self.clockmode = self.CLOCK_INTERNAL
+
+ def tick(self):
+ # if round(self.beats, 5) % 1 == 0:
+ # print "tick (%d active channels, %d pending events)" %
+ # (len(self.channels), len(self.events))
+ '''
+ # copy self.events because removing from it whilst using it = bad idea.
+ # perform events before channels are executed because an event might
+ # include scheduling a quantized channel, which should then be
+ # immediately evaluated.
+ '''
+ for event in self.events[:]:
+ '''
+ # the only event we currently get in a Timeline are add_channel events
+ # -- which have a raw function associated with them.
+ #
+ # round needed because we can sometimes end up with beats = 3.99999999...
+ # http://docs.python.org/tutorial/floatingpoint.html
+ '''
+ if round(event["time"], 8) <= round(self.beats, 8):
+ event["fn"]()
+ self.events.remove(event)
+
+ '''
+ # some devices (ie, MidiFileOut) require being told to tick
+ '''
+ for device in self.devices:
+ device.tick(self.ticklen)
+
+ '''
+ # copy self.channels because removing from it whilst using it = bad idea
+ '''
+ for channel in self.channels[:]:
+ channel.tick(self.ticklen)
+ if channel.finished:
+ self.channels.remove(channel)
+
+ if self.stop_when_done and len(self.channels) == 0:
+ raise StopIteration
+
+ '''
+ # TODO: should automator and channel inherit from a common superclass?
+ # one is continuous, one is discrete.
+ '''
+ for automator in self.automators[:]:
+ automator.tick(self.ticklen)
+ if automator.finished:
+ self.automators.remove(automator)
+
+ self.beats += self.ticklen
+
+ def dump(self):
+ """ Output a summary of this Timeline object
+ """
+ print "Timeline (clock: %s)" % ("external" if self.clockmode == self.CLOCK_EXTERNAL else "%sbpm" % self.bpm)
+
+ print " - %d devices" % len(self.devices)
+ for device in self.devices:
+ print " - %s" % device
+
+ print " - %d channels" % len(self.channels)
+ for channel in self.channels:
+ print" - %s" % channel
+
+ def reset_to_beat(self):
+ self.beats = round(self.beats)
+ for channel in self.channels:
+ channel.reset_to_beat()
+
+ def reset(self):
+ # print "tl reset!"
+ self.beats = -.0001
+ # XXX probably shouldn't have to do this - should channels read the tl
+ # ticks value?
+ for channel in self.channels:
+ channel.reset()
+
+ def background(self):
+ """ Run this Timeline in a background thread. """
+ self.thread = thread.start_new_thread(self.run, ())
+
+ def run(self, high_priority=True):
+ """ Create clock with 64th-beat granularity.
+ By default, attempts to run as a high-priority thread
+ (though requires being run as root to re-nice the process) """
+ try:
+ import os
+ os.nice(-20)
+ print "Timeline: Running as high-priority thread"
+ except:
+ pass
+
+ try:
+ '''
+ Start the clock. This might internal (eg a Clock object, running on
+ an independent thread), or external (eg a MIDI clock).
+ '''
+ if self.clockmode == self.CLOCK_INTERNAL:
+ self.clock.run(self)
+ else:
+ self.clocksource.run()
+
+ except StopIteration:
+ '''
+ # This will be hit if every Pattern in a timeline is exhausted.
+ '''
+ print "Timeline finished"
+
+ except Exception as e:
+ print " *** Exception in background Timeline thread: %s" % e
+ traceback.print_exc(file=sys.stdout)
+
+ def warp(self, warper):
+ self.clock.warp(warper)
+
+ def unwarp(self, warper):
+ self.clock.warp(warper)
+
+ def set_output(self, device):
+ self.devices.clear()
+ self.add_output(device)
+
+ def add_output(self, device):
+ self.devices.append(device)
+
+ def sched(self, event, quantize=0, delay=0, count=0, device=None):
+ if not device:
+ if not self.devices:
+ isobar.log("Adding default MIDI output")
+ self.add_output(isobar.io.MidiOut())
+ device = self.devices[0]
+ if self.max_channels and len(self.channels) >= self.max_channels:
+ print "*** timeline: refusing to schedule channel (hit limit of %d)" % self.max_channels
+ return
+
+ # hmm - why do we need to copy this?
+ # c = channel(copy.deepcopy(dict))
+ # c = Channel(copy.copy(dict))
+ def addchan():
+ #------------------------------------------------------------------
+ # this isn't exactly the best way to determine whether a device is
+ # an automator or event generator. should we have separate calls?
+ #------------------------------------------------------------------
+ if isinstance(event, dict) and "control" in event and False:
+ pass
+ else:
+ c = Channel(event, count)
+ c.timeline = self
+ c.device = device
+ self.channels.append(c)
+
+ if quantize or delay:
+ if quantize:
+ schedtime = quantize * \
+ math.ceil(float(self.beats + delay) / quantize)
+ else:
+ schedtime = self.beats + delay
+ self.events.append({'time': schedtime, 'fn': addchan})
+ else:
+ addchan()
+
class AutomatorChannel:
- def __init__(self, dict = {}):
- dict.setdefault('value', 0.5)
- dict.setdefault('control', 0)
- dict.setdefault('channel', 0)
- for k, value in dict.iteritems():
- if not isinstance(value, Pattern):
- dict[k] = PAConst(value)
+ def __init__(self, dict={}):
+ dict.setdefault('value', 0.5)
+ dict.setdefault('control', 0)
+ dict.setdefault('channel', 0)
+
+ for k, value in dict.iteritems():
+ if not isinstance(value, Pattern):
+ dict[k] = PAConst(value)
- self.dick = dict
+ self.dick = dict
+
+ def tick(self, ticklen):
+ pass
- def tick(self, ticklen):
- pass
class Channel:
- def __init__(self, events = {}, count = 0):
- #----------------------------------------------------------------------
- # evaluate in case we have a pattern which gives us an event
- # eg: PSeq([ { "note" : 20, "dur" : 0.5 }, { "note" : 50, "dur" : PWhite(0, 2) } ])
- #
- # is this ever even necessary?
- #----------------------------------------------------------------------
- # self.events = Pattern.pattern(events)
- self.events = events
-
- self.next()
-
- self.timeline = None
- self.pos = 0
- self.dur_now = 0
- self.phase_now = self.event["phase"].next()
- self.next_note = 0
-
- self.noteOffs = []
- self.finished = False
- self.count_max = count
- self.count_now = 0
-
- def __str__(self):
- return "Channel(pos = %d, note = %s, dur = %s, dur_now = %d, channel = %s, control = %s)[count = %d/%d])" % (self.pos, self.event["note"], self.event["dur"], self.dur_now, self.event["channel"], self.event["control"] if "control" in self.event else "-", self.count_now, self.count_max)
-
- def next(self):
- #----------------------------------------------------------------------
- # event is a dictionary of patterns. anything which is not a pattern
- # (eg, constant values) are turned into PConsts.
- #----------------------------------------------------------------------
- event = Pattern.value(self.events)
-
- event.setdefault('note', 60)
- event.setdefault('transpose', 0)
- event.setdefault('dur', 1)
- event.setdefault('amp', 64)
- event.setdefault('channel', 0)
- event.setdefault('omit', 0)
- event.setdefault('gate', 1.0)
- event.setdefault('phase', 0.0)
- event.setdefault('octave', 0)
-
- if event.has_key('key'):
- pass
- elif event.has_key('scale'):
- event['key'] = Key(0, event['scale'])
- else:
- event['key'] = Key(0, Scale.default)
-
- #----------------------------------------------------------------------
- # this does the job of turning constant values into (PConst) patterns.
- #----------------------------------------------------------------------
- for key, value in event.items():
- event[key] = Pattern.pattern(value)
-
- self.event = event
-
- def tick(self, time):
- #----------------------------------------------------------------------
- # process noteOffs before we play the next note, else a repeated note
- # with gate = 1.0 will immediately be cancelled.
- #----------------------------------------------------------------------
- self.processNoteOffs()
-
- try:
- if round(self.pos, 8) >= round(self.next_note + self.phase_now, 8):
- self.dur_now = self.event['dur'].next()
- self.phase_now = self.event['phase'].next()
-
- self.play()
-
- self.next_note += self.dur_now
-
- self.next()
-
- self.count_now += 1
- if self.count_max and self.count_now >= self.count_max:
- raise StopIteration
- except StopIteration:
- self.finished = True
-
- self.pos += time
-
- def reset_to_beat(self):
- self.pos = round(self.pos)
-
- def reset(self):
- self.pos = 0
- self.dur_now = 0
- self.next_note = 0
-
- def play(self):
- values = {}
- for key, pattern in self.event.items():
- # TODO: HACK!! to prevent stepping through dur twice (see 'tick' above')
- if key == "dur":
- value = self.dur_now
- elif key == "phase":
- value = self.phase_now
- else:
- value = pattern.next()
- values[key] = value
-
- #------------------------------------------------------------------------
- # print: Prints a value each time this event is triggered.
- #------------------------------------------------------------------------
- if "print" in values:
- print values["print"]
- return
-
- #------------------------------------------------------------------------
- # action: Carry out an action each time this event is triggered
- #------------------------------------------------------------------------
- if "action" in values:
- try:
- if "object" in values:
- object = values["object"]
- values["action"](object)
- else:
- values["action"]()
- except Exception, e:
- print "Exception when handling scheduled action: %s" % e
- import traceback
- traceback.print_exc()
- pass
-
- return
-
- #------------------------------------------------------------------------
- # control: Send a control value
- #------------------------------------------------------------------------
- if "control" in values:
- value = values["value"]
- channel = values["channel"]
- isobar.log("control (channel %d, control %d, value %d)", values["channel"], values["control"], values["value"])
- self.device.control(values["control"], values["value"], values["channel"])
- return
-
- #------------------------------------------------------------------------
+
+ def __init__(self, events={}, count=0):
+ #----------------------------------------------------------------------
+ # evaluate in case we have a pattern which gives us an event
+ # eg: PSeq([ { "note" : 20, "dur" : 0.5 }, { "note" : 50, "dur" : PWhite(0, 2) } ])
+ #
+ # is this ever even necessary?
+ #----------------------------------------------------------------------
+ # self.events = Pattern.pattern(events)
+ self.events = events
+
+ self.next()
+
+ self.timeline = None
+ self.pos = 0
+ self.dur_now = 0
+ self.phase_now = self.event["phase"].next()
+ self.next_note = 0
+
+ self.noteOffs = []
+ self.finished = False
+ self.count_max = count
+ self.count_now = 0
+
+ def __str__(self):
+ return "Channel(pos = %d, note = %s, dur = %s, dur_now = %d, channel = %s, control = %s)[count = %d/%d])" % (self.pos, self.event["note"], self.event[
+ "dur"], self.dur_now, self.event["channel"], self.event["control"] if "control" in self.event else "-", self.count_now, self.count_max)
+
+ def next(self):
+ #----------------------------------------------------------------------
+ # event is a dictionary of patterns. anything which is not a pattern
+ # (eg, constant values) are turned into PConsts.
+ #----------------------------------------------------------------------
+ event = Pattern.value(self.events)
+
+ event.setdefault('note', 60)
+ event.setdefault('transpose', 0)
+ event.setdefault('dur', 1)
+ event.setdefault('amp', 64)
+ event.setdefault('channel', 0)
+ event.setdefault('omit', 0)
+ event.setdefault('gate', 1.0)
+ event.setdefault('phase', 0.0)
+ event.setdefault('octave', 0)
+
+ if 'key' in event:
+ pass
+ elif 'scale' in event:
+ event['key'] = Key(0, event['scale'])
+ else:
+ event['key'] = Key(0, Scale.default)
+
+ #----------------------------------------------------------------------
+ # this does the job of turning constant values into (PConst) patterns.
+ #----------------------------------------------------------------------
+ for key, value in event.items():
+ event[key] = Pattern.pattern(value)
+
+ self.event = event
+
+ def tick(self, time):
+ #----------------------------------------------------------------------
+ # process noteOffs before we play the next note, else a repeated note
+ # with gate = 1.0 will immediately be cancelled.
+ #----------------------------------------------------------------------
+ self.processNoteOffs()
+
+ try:
+ if round(self.pos, 8) >= round(self.next_note + self.phase_now, 8):
+ self.dur_now = self.event['dur'].next()
+ self.phase_now = self.event['phase'].next()
+
+ self.play()
+
+ self.next_note += self.dur_now
+
+ self.next()
+
+ self.count_now += 1
+ if self.count_max and self.count_now >= self.count_max:
+ raise StopIteration
+ except StopIteration:
+ self.finished = True
+
+ self.pos += time
+
+ def reset_to_beat(self):
+ self.pos = round(self.pos)
+
+ def reset(self):
+ self.pos = 0
+ self.dur_now = 0
+ self.next_note = 0
+
+ def play(self):
+ values = {}
+ for key, pattern in self.event.items():
+ # TODO: HACK!! to prevent stepping through dur twice (see 'tick'
+ # above')
+ if key == "dur":
+ value = self.dur_now
+ elif key == "phase":
+ value = self.phase_now
+ else:
+ value = pattern.next()
+ values[key] = value
+
+ '''
+ print: Prints a value each time this event is triggered.
+ '''
+ if "print" in values:
+ print values["print"]
+ return
+
+ '''
+ action: Carry out an action each time this event is triggered
+ '''
+ if "action" in values:
+ try:
+ if "object" in values:
+ object = values["object"]
+ values["action"](object)
+ else:
+ values["action"]()
+ except Exception as e:
+ print "Exception when handling scheduled action: %s" % e
+ import traceback
+ traceback.print_exc()
+ pass
+
+ return
+
+ '''
+ control: Send a control value
+ '''
+ if "control" in values:
+ value = values["value"]
+ channel = values["channel"]
+ isobar.log(
+ "control (channel %d, control %d, value %d)",
+ values["channel"],
+ values["control"],
+ values["value"])
+ self.device.control(
+ values["control"],
+ values["value"],
+ values["channel"])
+ return
+
+ '''
# address: Send a value to an OSC endpoint
- #------------------------------------------------------------------------
- if "address" in values:
- self.device.send(values["address"], values["params"])
- return
-
- #------------------------------------------------------------------------
- # note/degree/etc: Send a MIDI note
- #------------------------------------------------------------------------
- note = None
-
- if "degree" in values:
- degree = values["degree"]
- key = values["key"]
- octave = values["octave"]
- if not degree is None:
- #----------------------------------------------------------------------
- # handle lists of notes (eg chords).
- # TODO: create a class which allows for scalars and arrays to handle
- # addition transparently
- #----------------------------------------------------------------------
- try:
- values["note"] = [ key[n] + (octave * 12) for n in degree ]
- except:
- values["note"] = key[degree] + (octave * 12)
-
- #----------------------------------------------------------------------
- # For cases in which we want to introduce a rest, simply set our 'amp'
- # value to zero. This means that we can still send rest events to
- # devices which receive all generic events (useful to display rests
- # when rendering a score).
- #----------------------------------------------------------------------
- if random.uniform(0, 1) < values['omit']:
- values["note"] = None
-
- if values["note"] is None:
- # print "(rest)"
- # return
- values["note"] = 0
- values["amp"] = 0
- else:
- #----------------------------------------------------------------------
- # handle lists of notes (eg chords).
- # TODO: create a class which allows for scalars and arrays to handle
- # addition transparently.
- #
- # the below does not allow for values["transpose"] to be an array,
- # for example.
- #----------------------------------------------------------------------
- try:
- values["note"] = [ note + values["transpose"] for note in values["note"] ]
- except:
- values["note"] += values["transpose"]
-
-
- #----------------------------------------------------------------------
- # event: Certain devices (eg Socket IO) handle generic events,
- # rather than noteOn/noteOff. (Should probably have to
- # register for this behaviour rather than happening magically...)
- #----------------------------------------------------------------------
- if hasattr(self.device, "event") and callable(getattr(self.device, "event")):
- d = copy.copy(values)
- for key, value in d.items():
- #------------------------------------------------------------------------
- # turn non-builtin objects into their string representations.
- # we don't want to call repr() on numbers as it turns them into strings,
- # which we don't want to happen in our resultant JSON.
- # TODO: there absolutely must be a way to do this for all objects which are
- # non-builtins... ie, who are "class" instances rather than "type".
- #
- # we could check dir(__builtins__), but for some reason, __builtins__ is
- # different here than it is outside of a module!?
- #
- # instead, go with the lame option of listing "primitive" types.
- #------------------------------------------------------------------------
- if type(value) not in (int, float, bool, str, list, dict, tuple):
- name = type(value).__name__
- value = repr(value)
- d[key] = value
-
- self.device.event(d)
- return
-
- #----------------------------------------------------------------------
- # noteOn: Standard (MIDI) type of device
- #----------------------------------------------------------------------
- if values["amp"] > 0:
- # TODO: pythonic duck-typing approach might be better
- # TODO: doesn't handle arrays of amp, channel values, etc
- notes = values["note"] if hasattr(values["note"], '__iter__') else [ values["note"] ]
-
- #----------------------------------------------------------------------
- # Allow for arrays of amp, gate etc, to handle chords properly.
- # Caveat: Things will go horribly wrong for an array of amp/gate values
- # shorter than the number of notes.
- #----------------------------------------------------------------------
- for index, note in enumerate(notes):
- amp = values["amp"][index] if isinstance(values["amp"], list) else values["amp"]
- channel = values["channel"][index] if isinstance(values["channel"], list) else values["channel"]
- gate = values["gate"][index] if isinstance(values["gate"], list) else values["gate"]
-
- isobar.log("note on (channel %d, note %d, velocity %d)", channel, note, amp);
- self.device.noteOn(note, amp, channel)
-
- note_dur = self.dur_now * gate
- self.schedNoteOff(self.next_note + note_dur + self.phase_now, note, channel)
-
- def schedNoteOff(self, time, note, channel):
- self.noteOffs.append([ time, note, channel ])
-
- def processNoteOffs(self):
- for n, note in enumerate(self.noteOffs):
- # TODO: create a Note object to represent these noteOff events
- if note[0] <= self.pos:
- index = note[1]
- channel = note[2]
- isobar.log("note off (channel %d, note %d)", channel, index);
- self.device.noteOff(index, channel)
- self.noteOffs.pop(n)
+ '''
+ if "address" in values:
+ self.device.send(values["address"], values["params"])
+ return
+
+ '''
+ # note/degree/etc: Send a MIDI note
+ '''
+ note = None
+
+ if "degree" in values:
+ degree = values["degree"]
+ key = values["key"]
+ octave = values["octave"]
+ if degree is not None:
+ '''
+ handle lists of notes (eg chords).
+ TODO: create a class which allows for scalars and arrays to
+ handle addition transparently
+ '''
+ try:
+ values["note"] = [key[n] + (octave * 12) for n in degree]
+ except:
+ values["note"] = key[degree] + (octave * 12)
+
+ #----------------------------------------------------------------------
+ # For cases in which we want to introduce a rest, simply set our 'amp'
+ # value to zero. This means that we can still send rest events to
+ # devices which receive all generic events (useful to display rests
+ # when rendering a score).
+ #----------------------------------------------------------------------
+ if random.uniform(0, 1) < values['omit']:
+ values["note"] = None
+
+ if values["note"] is None:
+ # print "(rest)"
+ # return
+ values["note"] = 0
+ values["amp"] = 0
+ else:
+ #------------------------------------------------------------------
+ # handle lists of notes (eg chords).
+ # TODO: create a class which allows for scalars and arrays to handle
+ # addition transparently.
+ #
+ # the below does not allow for values["transpose"] to be an array,
+ # for example.
+ #------------------------------------------------------------------
+ try:
+ values["note"] = [note + values["transpose"]
+ for note in values["note"]]
+ except:
+ values["note"] += values["transpose"]
+
+ #----------------------------------------------------------------------
+ # event: Certain devices (eg Socket IO) handle generic events,
+ # rather than noteOn/noteOff. (Should probably have to
+ # register for this behaviour rather than happening magically...)
+ #----------------------------------------------------------------------
+ if hasattr(
+ self.device,
+ "event") and callable(
+ getattr(
+ self.device,
+ "event")):
+ d = copy.copy(values)
+ for key, value in d.items():
+ '''
+ # turn non-builtin objects into their string representations.
+ # we don't want to call repr() on numbers as it turns them into strings,
+ # which we don't want to happen in our resultant JSON.
+ # TODO: there absolutely must be a way to do this for all objects which are
+ # non-builtins... ie, who are "class" instances rather than "type".
+ #
+ # we could check dir(__builtins__), but for some reason, __builtins__ is
+ # different here than it is outside of a module!?
+ #
+ # instead, go with the lame option of listing "primitive" types.
+ '''
+ if type(value) not in (
+ int,
+ float,
+ bool,
+ str,
+ list,
+ dict,
+ tuple):
+ name = type(value).__name__
+ value = repr(value)
+ d[key] = value
+
+ self.device.event(d)
+ return
+
+ #----------------------------------------------------------------------
+ # noteOn: Standard (MIDI) type of device
+ #----------------------------------------------------------------------
+ if values["amp"] > 0:
+ # TODO: pythonic duck-typing approach might be better
+ # TODO: doesn't handle arrays of amp, channel values, etc
+ notes = values["note"] if hasattr(
+ values["note"],
+ '__iter__') else [
+ values["note"]]
+
+ #------------------------------------------------------------------
+ # Allow for arrays of amp, gate etc, to handle chords properly.
+ # Caveat: Things will go horribly wrong for an array of amp/gate values
+ # shorter than the number of notes.
+ #------------------------------------------------------------------
+ for index, note in enumerate(notes):
+ amp = values["amp"][index] if isinstance(
+ values["amp"],
+ list) else values["amp"]
+ channel = values["channel"][index] if isinstance(
+ values["channel"],
+ list) else values["channel"]
+ gate = values["gate"][index] if isinstance(
+ values["gate"],
+ list) else values["gate"]
+
+ isobar.log(
+ "note on (channel %d, note %d, velocity %d)",
+ channel,
+ note,
+ amp)
+ self.device.noteOn(note, amp, channel)
+
+ note_dur = self.dur_now * gate
+ self.schedNoteOff(
+ self.next_note +
+ note_dur +
+ self.phase_now,
+ note,
+ channel)
+
+ def schedNoteOff(self, time, note, channel):
+ self.noteOffs.append([time, note, channel])
+
+ def processNoteOffs(self):
+ for n, note in enumerate(self.noteOffs):
+ # TODO: create a Note object to represent these noteOff events
+ if note[0] <= self.pos:
+ index = note[1]
+ channel = note[2]
+ isobar.log("note off (channel %d, note %d)", channel, index)
+ self.device.noteOff(index, channel)
+ self.noteOffs.pop(n)
#----------------------------------------------------------------------
# a clock is relied upon to generate accurate tick() events every
@@ -492,44 +537,45 @@ def processNoteOffs(self):
# as per MIDI
#----------------------------------------------------------------------
+
class Clock:
- def __init__(self, ticksize = 1.0/24):
- self.ticksize_orig = ticksize
- self.ticksize = ticksize
- self.warpers = []
- self.accelerate = 1.0
-
- def run(self, timeline):
- clock0 = clock1 = time.time() * self.accelerate
- try:
- #------------------------------------------------------------------------
- # allow a tick to elapse before we call tick() for the first time
- # to keep Warp patterns in sync
- #------------------------------------------------------------------------
- while True:
- if clock1 - clock0 >= self.ticksize:
- # time for a tick
- timeline.tick()
- clock0 += self.ticksize
- self.ticksize = self.ticksize_orig
- for warper in self.warpers:
- warp = warper.next()
- #------------------------------------------------------------------------
- # map [-1..1] to [0.5, 2]
- # - so -1 doubles our tempo, +1 halves it
- #------------------------------------------------------------------------
- warp = pow(2, warp)
- self.ticksize *= warp
-
- time.sleep(0.002)
- clock1 = time.time() * self.accelerate
- except KeyboardInterrupt:
- print "interrupt caught, exiting"
- return
-
- def warp(self, warper):
- self.warpers.append(warper)
-
- def unwarp(self, warper):
- self.warpers.remove(warper)
+ def __init__(self, ticksize=1.0 / 24):
+ self.ticksize_orig = ticksize
+ self.ticksize = ticksize
+ self.warpers = []
+ self.accelerate = 1.0
+
+ def run(self, timeline):
+ clock0 = clock1 = time.time() * self.accelerate
+ try:
+ '''
+ # allow a tick to elapse before we call tick() for the first time
+ # to keep Warp patterns in sync
+ '''
+ while True:
+ if clock1 - clock0 >= self.ticksize:
+ # time for a tick
+ timeline.tick()
+ clock0 += self.ticksize
+ self.ticksize = self.ticksize_orig
+ for warper in self.warpers:
+ warp = warper.next()
+ '''
+ # map [-1..1] to [0.5, 2]
+ # - so -1 doubles our tempo, +1 halves it
+ '''
+ warp = pow(2, warp)
+ self.ticksize *= warp
+
+ time.sleep(0.002)
+ clock1 = time.time() * self.accelerate
+ except KeyboardInterrupt:
+ print "interrupt caught, exiting"
+ return
+
+ def warp(self, warper):
+ self.warpers.append(warper)
+
+ def unwarp(self, warper):
+ self.warpers.remove(warper)
diff --git a/isobar/util.py b/isobar/util.py
index 2a7998e..6d412f7 100644
--- a/isobar/util.py
+++ b/isobar/util.py
@@ -4,105 +4,119 @@
import sys
note_names = [
- [ "C" ],
- [ "C#", "Db" ],
- [ "D" ],
- [ "D#", "Eb" ],
- [ "E" ],
- [ "F" ],
- [ "F#", "Gb" ],
- [ "G" ],
- [ "G#", "Ab" ],
- [ "A" ],
- [ "A#", "Bb" ],
- [ "B" ]
+ ["C"],
+ ["C#", "Db"],
+ ["D"],
+ ["D#", "Eb"],
+ ["E"],
+ ["F"],
+ ["F#", "Gb"],
+ ["G"],
+ ["G#", "Ab"],
+ ["A"],
+ ["A#", "Bb"],
+ ["B"]
]
isobar.debug = False
+
def log(message, *args):
- if isobar.debug:
- message = message % args
- sys.stderr.write("[isobar] %s\n" % message)
+ if isobar.debug:
+ message = message % args
+ sys.stderr.write("[isobar] %s\n" % message)
+
def normalize(array):
- """ Normalise an array to sum to 1.0. """
- if sum(array) == 0:
- return array
- return map(lambda n: float(n) / sum(array), array)
+ """ Normalise an array to sum to 1.0. """
+ if sum(array) == 0:
+ return array
+ return map(lambda n: float(n) / sum(array), array)
+
def windex(weights):
- """ Return a random index based on a list of weights, from 0..(len(weights) - 1).
- Assumes that weights is already normalised. """
- n = random.uniform(0, 1)
- for i in range(len(weights)):
- if n < weights[i]:
- return i
- n = n - weights[i]
+ """ Return a random index based on a list of weights, from 0..(len(weights) - 1).
+ Assumes that weights is already normalised. """
+ n = random.uniform(0, 1)
+ for i in range(len(weights)):
+ if n < weights[i]:
+ return i
+ n = n - weights[i]
+
def wnindex(weights):
- """ Returns a random index based on a list of weights.
- Normalises list of weights before executing. """
- wnorm = normalize(weights)
- return windex(wnorm)
+ """ Returns a random index based on a list of weights.
+ Normalises list of weights before executing. """
+ wnorm = normalize(weights)
+ return windex(wnorm)
+
def wchoice(array, weights):
- """ Performs a weighted choice from a list of values (assumes pre-normalised weights) """
- index = windex(weights)
- return array[index]
+ """ Performs a weighted choice from a list of values (assumes pre-normalised weights) """
+ index = windex(weights)
+ return array[index]
+
def wnchoice(array, weights):
- """ Performs a weighted choice from a list of values
- (does not assume pre-normalised weights). """
- index = wnindex(weights)
- return array[index]
+ """ Performs a weighted choice from a list of values
+ (does not assume pre-normalised weights). """
+ index = wnindex(weights)
+ return array[index]
+
def nametomidi(name):
- """ Maps a MIDI note name (D3, C#6) to a value.
- Assumes that middle C is C4. """
- if name[-1].isdigit():
- octave = int(name[-1])
- name = name[:-1]
- else:
- octave = 0
-
- try:
- index = note_names.index(filter(lambda nameset: name in nameset, note_names)[0])
- return octave * 12 + index
- except:
- return None
+ """ Maps a MIDI note name (D3, C#6) to a value.
+ Assumes that middle C is C4. """
+ if name[-1].isdigit():
+ octave = int(name[-1])
+ name = name[:-1]
+ else:
+ octave = 0
+
+ try:
+ index = note_names.index(
+ filter(
+ lambda nameset: name in nameset,
+ note_names)[0])
+ return octave * 12 + index
+ except:
+ return None
+
def miditopitch(note):
- """ Maps a MIDI note index to a note name (independent of octave)
- miditopitch(0) -> "C"
- miditopitch(1) -> "C#" """
- degree = int(note) % len(note_names)
- return note_names[degree][0]
+ """ Maps a MIDI note index to a note name (independent of octave)
+ miditopitch(0) -> "C"
+ miditopitch(1) -> "C#" """
+ degree = int(note) % len(note_names)
+ return note_names[degree][0]
+
def miditoname(note):
- """ Maps a MIDI note index to a note name. """
- degree = int(note) % len(note_names)
- octave = int(note / len(note_names)) - 1
- str = "%s%d" % (note_names[degree][0], octave)
- frac = math.modf(note)[0]
- if frac > 0:
- str = (str + " + %2f" % frac)
+ """ Maps a MIDI note index to a note name. """
+ degree = int(note) % len(note_names)
+ octave = int(note / len(note_names)) - 1
+ str = "%s%d" % (note_names[degree][0], octave)
+ frac = math.modf(note)[0]
+ if frac > 0:
+ str = (str + " + %2f" % frac)
- return str
+ return str
-def bipolar_diverge(maximum):
- """ returns [0, 1, -1, ...., maximum, -maximum ] """
- sequence = list(sum(zip(range(maximum + 1), range(0, -maximum - 1, -1)), ()))
- sequence.pop(0)
- return sequence
-
-def filter_tone_row(source, target, bend_limit = 7):
- """ filters the notes in