The Easiest Way to Save and Share Code Snippets on the web

Slipmat Lead-In

python

posted: May, 4th 2010 | jump to bottom

#!/usr/bin/env python
#
# Slipmat Lead-In
#
# Copyright (c) 2010 Jacob Joaquin
# Email jacobjoaquin@gmail.com
# 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')
 
278 views