Skip to content

Instantly share code, notes, and snippets.

@jacobjoaquin
Created May 6, 2010 18:44
Show Gist options
  • Save jacobjoaquin/392530 to your computer and use it in GitHub Desktop.
Save jacobjoaquin/392530 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
#
# Slipmat Lead-In
#
# Copyright (c) 2010 Jacob Joaquin
# Email [email protected]
# Visit Slipmat -- http://slipmat.noisepages.com/
#
# Code License: GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007
# http://www.gnu.org/licenses/lgpl-3.0.txt
#
# Music License:
# This work is licensed under the Creative Commons Attribution 3.0
# United States License. To view a copy of this license, visit
# http://creativecommons.org/licenses/by/3.0/us/ or send a letter to
# Creative Commons, 171 Second Street, Suite 300, San Francisco,
# California, 94105, USA.
import math
import operator
import struct
sr = 44100
ksmps = 1
class UnitGenerator:
'''All unit generator classes inherit from this.'''
def __init__(self): pass
def __iter__(self): pass
def next(self): raise StopIteration
def __add__(self, ugen): return Add(self, ugen)
def __mul__(self, ugen): return Mul(self, ugen)
class Instr():
'''Decorator for creating a UnitGenerator from a definition.'''
def __init__(self, ugen_def): self.ugen_def = ugen_def
def __call__(self, *args): return self.Iter(self.ugen_def, *args)
class Iter(UnitGenerator):
def __init__(self, ugen_def, *args): self.ugen_def = ugen_def(*args)
def __iter__(self):
self.index = 0
self._iter = (i for i in self.ugen_def)
return self
def next(self):
if self.index >= ksmps: raise StopIteration
self.index += 1
return self._iter.next()
class IterReduce(UnitGenerator):
'''Reduces a list of iterators with the op function.'''
op = operator.add
def __init__(self, *ugens):
self.ugens = ugens
def __iter__(self):
self.index = 0
self.iters = [(j for j in i) for i in self.ugens]
return self
def next(self):
if self.index >= ksmps: raise StopIteration
self.index += 1
return reduce(self.op, (i.next() for i in self.iters))
class Mul(IterReduce): op = operator.mul
class Add(IterReduce): pass
class RiseFall(UnitGenerator):
'''A rise-fall envelope generator.'''
def __init__(self, dur, peak=0.5):
self.frames = sec_to_frames(dur)
self.rise = int(peak * self.frames)
self.fall = int(self.frames - self.rise)
self.inc = 0
self.v = 0
def __iter__(self):
self.index = 0
if self.inc <= self.rise and self.rise != 0:
self.v = self.inc / float(self.rise)
else:
self.v = (self.fall - (self.inc - self.rise)) / float(self.fall)
self.inc += 1
return self
def next(self):
if self.index >= ksmps: raise StopIteration
self.index += 1
return self.v
class Sine(UnitGenerator):
'''A sine wave oscillator.'''
def __init__(self, amp=1.0, freq=440, phase=0.0):
self.amp = amp
self.freq = float(freq)
self.phase = phase
def __iter__(self):
self.index = 0
return self
def next(self):
if self.index >= ksmps: raise StopIteration
self.index += 1
v = math.sin(self.phase * 2 * math.pi)
self.phase += self.freq / sr
return v * self.amp
class UVal(UnitGenerator):
'''Convert a numeric value to a unit generator object'''
def __init__(self, v):
self.v = v
def __iter__(self):
self.index = 0
return self
def next(self):
if self.index >= ksmps: raise StopIteration
self.index += 1
return self.v
class ScoreEvents:
'''Schedule events for unit generators.'''
def __init__(self, tempo=60):
self.tempo = tempo
self.event_dict = {}
self.ID = 0
self.last_frame = 0
def event(self, start, dur, ugen):
ugen_start = sec_to_frames(start * 60 / float(self.tempo))
ugen_end = ugen_start + sec_to_frames(dur * 60 / float(self.tempo))
self.last_frame = max(ugen_start, ugen_end, self.last_frame)
if ugen_start not in self.event_dict.keys():
self.event_dict.update({ugen_start: [(self.ID, 'start', ugen)]})
else:
self.event_dict[ugen_start].append((self.ID, 'start', ugen))
if ugen_end not in self.event_dict.keys():
self.event_dict.update({ugen_end: [(self.ID, 'stop', None)]})
else:
self.event_dict[ugen_end].append((self.ID, 'stop', None))
self.ID += 1
def cpspch(p):
'''Convert pitch class to frequency.'''
octave, note = divmod(p, 1)
return 440 * 2 ** (((octave - 8) * 12 + ((note * 100) - 9)) / 12.0)
def sec_to_frames(dur): return int(dur * sr / float(ksmps))
def ScoreEventsToWave(score, filename):
events = {}
wave = open(filename, 'wb')
chunk_2_size = score.last_frame * ksmps * 2
wave.write(struct.pack('< 4s I 4s', 'RIFF', 36 + chunk_2_size, 'WAVE'))
wave.write(struct.pack('< 4s I 2h 2I 2h', 'fmt ', 16, 1, 1, sr, \
sr * 2, 2, 16))
wave.write(struct.pack('< 4s I', 'data', chunk_2_size))
for f in range(score.last_frame + 1):
print '%d:' % f
if f in score.event_dict:
for L in score.event_dict[f]:
ID, command, function = L
if command == 'start':
events.update({ID: function})
elif command == 'stop':
del events[ID]
iters = [(j for j in v) for v in events.itervalues()]
for i in range(ksmps):
if iters:
v = reduce(operator.add, (i.next() for i in iters))
else:
v = 0
if v > 1: v = 1
if v < -1: v = -1
wave.write(struct.pack('h', int(v * 32767)))
wave.close()
if __name__ == "__main__":
import random
# Instruments
@Instr
def RingTine(dur, amp, freq_1, freq_2, peak=0):
'''Two ring modulated sine waves with an amplitude envelope.'''
return Sine(amp, freq_1) * Sine(amp, freq_2) * RiseFall(dur, peak)
@Instr
def DirtySine(dur, amp, freq, peak):
amp = 1.0 / 1.44 * amp
a1 = Sine(1, freq)
a2 = Sine(0.1, freq * 3)
a3 = Sine(0.24, freq * 5)
a4 = Sine(0.1, freq * 1.15)
ring = a1 + (a2 + a3 + a4) * Sine(1, 1.003)
return ring * RiseFall(dur, peak) * UVal(amp)
# Event Generators
def dusty_vinyl(s, start, dur, amp, freq_min, freq_max, density):
'''Granular Sine Event Generator.'''
for i in range(int(density * dur)):
freq = random.random() * (freq_max - freq_min) + freq_min
t = random.random() * (dur - start) + start
s.event(t, 1 / freq, Sine(amp * random.random(), freq))
def sine_arp(s, start, bars, res, amp, note_list, decay):
offset = start
b = 60.0 / s.tempo # duration of a beat in seconds
for bar in range(bars):
n = 0
while n < 4.0 / res:
note = cpspch(note_list[n % len(note_list)])
ugen = Sine(amp, note) * RiseFall(decay * b, 0)
s.event(offset, decay, ugen)
n += 1
offset = start + bar * 4 + n * res
# Score
s = ScoreEvents(tempo=169)
b = 60.0 / s.tempo
dusty_vinyl(s, 0, 80, 0.25, 1000, 10000, 30 * 60 / 169.0)
s.event(6, 16, DirtySine(16 * b, 0.15, cpspch(8.07), 0.95))
s.event(22, 4, RingTine(4, 0.5, cpspch(10.10), 55, 0))
n = [8.00, 8.03, 8.02, 8.07, 8.05, 8.10, 8.09, 9.00]
sine_arp(s, 22, 8, 0.25, 0.1, n, 0.8)
s.event(26, 9, RingTine(9, 0.3, cpspch(9.03), 33, 0.9))
s.event(33, 9, RingTine(9, 0.5, cpspch(8.10), 55, 0))
s.event(40, 9, RingTine(9, 0.25, cpspch(7.00), 11, 0))
s.event(54.5, 9, RingTine(9 * b, 0.3, cpspch(9.03), 33, 0.1))
s.event(54, 9, RingTine(9 * b, 0.5, cpspch(8.07), 44, 0))
s.event(54, 8, DirtySine(8 * b, 0.15, cpspch(7.07), 0.1))
s.event(60, 4, DirtySine(4 * b, 0.15, cpspch(6.07), 0.1))
ScoreEventsToWave(s, 'slipmat_lead-in.wav')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment