Created
May 6, 2010 18:44
-
-
Save jacobjoaquin/392530 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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