#!/usr/bin/python from __future__ import division import sys import random import wave def resample_input(filename, rate): sample = wave.open(filename, 'r') assert sample.getnchannels() == 1 assert sample.getcomptype() == 'NONE' freq = sample.getframerate() assert sample.getsampwidth() == 1 # until I implement more contents = sample.readframes(sample.getnframes()) # A sum table. rv = [] total = 0 # Very crude resampling. for ii, sample_byte in enumerate(contents): while len(rv) < (ii+1) * rate / freq: total += ord(sample_byte) rv.append(total) return rv class DitherClipper: def __init__(self): self.err = 0 def dither(self, val): val += 128 val_int = int(val + 0.5) self.err += val_int - val if self.err < -0.55: self.err += 1 val_int += 1 elif self.err > 0.55: self.err -= 1 val_int -= 1 if val_int < 0: self.err += -val_int val_int = 0 elif val_int > 255: self.err -= val_int - 255 val_int = 255 return val_int class Note: def __init__(self, sample, speed, volume): self.sample = sample self.speed = speed self.volume = volume self.fp = 0 def __iter__(self): return self def next(self): samp = self.sample fp = self.fp if fp == len(samp)-1: raise StopIteration nfp = min(fp + self.speed, len(samp)-1) infp, ifp = int(nfp), int(fp) assert infp != ifp, (nfp, fp, len(samp), self.speed) avg = (samp[infp] - samp[ifp]) / (infp - ifp) self.fp = nfp return (avg - 128) * self.volume class Mixer: def __init__(self): self.notes = [] def add(self, note): self.notes.append(note) def __iter__(self): return self def next(self): samples = [] ii = 0 while ii < len(self.notes): note = self.notes[ii] try: samples.append(note.next()) except StopIteration: self.notes.pop(ii) ii += 1 return sum(samples) def __len__(self): return len(self.notes) def main(argv, output): output_rate = 8000 oversampling = 16 samples = [resample_input(filename, output_rate * oversampling) for filename in argv[1:]] mixer = Mixer() clipper = DitherClipper() while True: while len(mixer) < 5: tone = random.randrange(-48, 13) ratio = 2**(tone/12) mixer.add(Note(random.choice(samples), speed = oversampling * ratio, volume = 2 ** random.uniform(-3, 0))) output.write(chr(clipper.dither(mixer.next()))) if __name__ == '__main__': main(sys.argv, sys.stdout)