User:Tamas Bates/NetProto/PyAudio
Random Raw WAV generation
This script generates semi-random WAV audio by mixing together several flexible sound structures. To keep the sound output from being too horrible, a core frequency is defined (randomly chosen at runtime), and all notes generated in the output are made from frequencies harmonic to the core. This is not quite sufficient, and the program still simply generates horrible noise nine times out of ten. The sound structures mentioned above simply play one or more notes according to a predefined pattern (e.g. play a set of notes in sequence, play a combination of notes as a chord, and so on). The behavior of each is parametrized to allow for some degree of flexibility, and many instances of each structure are created with random values each time the script runs in order to generate different types of output.
Output samples: File:PyAudio4.ogg File:PyAudio2.ogg File:PyAudio1.ogg File:PyAudio3.ogg
Usage: audio.py [-f Filename] [-l Duration] This will output Duration seconds (approx.) of data to Filename. Omitting the -f argument will cause the program to send all data to stdout. Unfortunately, it doesn't generate samples fast enough to play the content in real-time, so this is of limited use...
The code below is in need of some revision, but such is life...
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import wave, struct, math, argparse
import math, random, sys, threading
from abc import ABCMeta, abstractmethod, abstractproperty
debug = False
nframes=0
nchannels=2
sampwidth=2 # in bytes so 2=16bit, 1=8bit
samplerate=44100
bufsize=2048
max_amplitude = float(int((2 ** (sampwidth * 8)) / 2) - 1)
max_interval_length = 8000
def printDebug(str):
if debug:
print(str)
#--------------------------------#
# Notes generate a constant tone #
#--------------------------------#
class Note():
def __init__(self, SampleRate, Frequency, Balance):
self.timeup = ((SampleRate / Frequency) * Balance) / 2
self.timedown = (SampleRate / Frequency) / 2
self.time = 0
self.Up = True
@property
def period(self):
return self.timeup + self.timedown
def getSample(self):
self.time += 1
if self.Up:
if self.time > self.timeup:
self.time = 0
self.Up = False
return 0.1
else:
if self.time > self.timedown:
self.time = 0
self.Up = True
return -0.1
class Silence(Note):
def __init__(self):
return
def getSample(self):
return 0
#--------------------------------#
# Instruments generate sequences #
# of multiple Notes #
#--------------------------------#
class Instrument(object):
__metaclass__ = ABCMeta
def get_baseHarmonic(self):
return self.baseHarmonic
def set_baseHarmonic(self, value):
self.baseHarmonic = value
_baseHarmonic = abstractproperty(get_baseHarmonic, set_baseHarmonic)
@abstractmethod
def nextSample(self):
return NotImplemented
@abstractmethod
def reset(self):
pass
class ConstantInstrument(Instrument):
def __init__(self, note):
self.note = note
def get_baseHarmonic(self):
return super(ScaleInstrument, self).get_baseHarmonic()
def set_baseHarmonic(self, value):
super(ScaleInstrument, self).set_baseHarmonic(value)
_baseHarmonic = property(get_baseHarmonic, set_baseHarmonic)
def reset(self):
return
def nextSample(self):
return self.note.getSample()
class BeatInstrument(Instrument):
def __init__(self, note, beat, samplerate):
self.note = note
self.silence = Silence()
self.duration = (beat / 1000.0) * samplerate
self.time = 0
self.silent = False
def get_baseHarmonic(self):
return super(BeatInstrument, self).get_baseHarmonic()
def set_baseHarmonic(self, value):
super(BeatInstrument, self).set_baseHarmonic(value)
_baseHarmonic = property(get_baseHarmonic, set_baseHarmonic)
def reset(self):
return
def nextSample(self):
if self.silent:
sample = self.silence.getSample()
self.time += 1
if self.time > self.duration:
self.time = 0
self.silent = False
else:
sample = self.note.getSample()
self.time += 1
if self.time > self.duration:
self.time = 0
self.silent = True
return sample
class ScaleInstrument(Instrument):
# notes should be a sorted list
def __init__(self, notes, duration, sampleRate, bounce):
self.scale = notes
self.curNote = 0
self.time = 0
self.duration = (duration / 1000.0) * sampleRate # duration == number of samples to play each note
self.dir = 1
self.bounce = bounce
def get_baseHarmonic(self):
return super(ScaleInstrument, self).get_baseHarmonic()
def set_baseHarmonic(self, value):
super(ScaleInstrument, self).set_baseHarmonic(value)
_baseHarmonic = property(get_baseHarmonic, set_baseHarmonic)
def reset(self):
self.curNote = 0
self.time = 0
def nextSample(self):
sample = self.scale[self.curNote].getSample()
self.time += 1
if self.time > self.duration:
self.time = 0
self.curNote += self.dir
if (self.curNote >= len(self.scale)) or (self.curNote < 0):
if self.bounce: # change direction
self.curNote = self.curNote - self.dir
self.dir = self.dir * -1
else: # loop
self.curNote = 0
return sample
class VibratoInstrument(Instrument):
def __init__(self, note, stretch):
self.note = note
self.silence = Silence()
self.silentDur = stretch
self.silent = False
self.time = 0
def get_baseHarmonic(self):
return super(ScaleInstrument, self).get_baseHarmonic()
def set_baseHarmonic(self, value):
super(ScaleInstrument, self).set_baseHarmonic(value)
_baseHarmonic = property(get_baseHarmonic, set_baseHarmonic)
def reset(self):
self.time = 0
def nextSample(self):
if self.silent:
sample = self.silence.getSample()
self.time += 1
if self.time > self.silentDur:
self.time = 0
self.silent = False
else:
sample = self.note.getSample()
self.time += 1
if self.time > self.note.period:
self.time = 0
self.silent = True
return sample
class ChordInstrument(Instrument):
def __init__(self, notes):
self.notes = notes
def get_baseHarmonic(self):
return super(ChordInstrument, self).get_baseHarmonic()
def set_baseHarmonic(self, value):
super(ChordInstrument, self).set_baseHarmonic(value)
_baseHarmonic = property(get_baseHarmonic, set_baseHarmonic)
def reset(self):
return
def nextSample(self):
sample = 0
for n in self.notes:
sample += n.getSample()
return sample
class RandChordInstrument(ChordInstrument):
def __init__(self, baseHarmonic, nNotes, sampleRate):
self.baseHarmonic = baseHarmonic
self.nNotes = nNotes
self.sampleRate = sampleRate
self.reset()
def reset(self):
self.notes = []
for _ in range(self.nNotes):
self.notes.append(Note(self.sampleRate, random.randint(1, 10) * self.baseHarmonic, 1))
class SampleManager():
def __init__(self, SampleRate):
self.sampleRate = SampleRate
self.time = -1
self.instruments = {}
self.intervals = []
self.curInterval = -1
self.intervalEnd = 0
def nextSample(self):
amp = 0
self.time += 1
if self.time >= self.intervalEnd: # need to move to next interval
self.curInterval += 1
if self.curInterval == len(self.intervals): # out of intervals to play
return None
self.intervalEnd = self.time + self.intervals[self.curInterval]['length']
printDebug("interval %s: %s" % (self.curInterval, self.intervals[self.curInterval]))
for instrument in self.intervals[self.curInterval]['instruments']:
amp += self.instruments[instrument].nextSample() * max_amplitude
if amp > max_amplitude: amp = max_amplitude
if amp < max_amplitude*-1: amp = max_amplitude*-1
#sys.stderr.write('%s: %s\n' % (str(self.time), str(amp)))
#printDebug("Sample #:%s; amp: %s" % (self.time, amp))
return struct.pack('h', int(amp)) #+ struct.pack('h', int(amp))
def nextFrame(self):
output = self.nextSample()
if output is not None:
output += output #stereo
return output
# Adds an audio segment
# length should be in ms, instruments should be a list of names
def addInterval(self, length, instruments):
nSamples = (length / 1000.0) * self.sampleRate
self.intervals.append({'length' : nSamples, 'instruments' : instruments})
# Adds the list of interval data
def addIntervals(self, intervals):
for i in intervals:
nSamples = (i[0] / 1000.0) * self.sampleRate
self.intervals.append({'length' : nSamples, 'instruments' : i[1]})
# Adds an "instrument"
def addInstrument(self, name, instrument):
self.instruments[name] = instrument
# Removes all instruments
def clearInstruments(self):
self.instruments.clear()
#--------------------#
# BEGIN MAIN PROGRAM #
#--------------------#
def RandIntervalsFromDuration(duration):
intervals = []
remaining = duration
while remaining > 0:
next = random.randint(1, max_interval_length)
intervals.append(next)
remaining -= next
return intervals
def RandNote(baseFreq, samplerate):
return Note(samplerate, random.randint(1,7) * baseFreq, 1)
def RandNotes(baseFreq, samplerate):
notes = []
nNotes = random.randint(2, 4)
for _ in range(nNotes):
notes.append(RandNote(baseFreq, samplerate))
return notes
def RandScale(baseFreq, samplerate):
scale = []
nNotes = random.randint(2, 7)
for i in range(nNotes):
scale.append(Note(samplerate, baseFreq * (1 + i), 1))
return scale
def RandInstruments(baseFreq, samplerate):
instruments = []
nInstruments = random.randint(5, 15)
for _ in range(nInstruments):
i = random.randint(0, 100)
if i < 10:
instruments.append(ConstantInstrument(RandNote(baseFreq, samplerate)))
elif i < 50:
instruments.append(ScaleInstrument(RandScale(baseFreq, samplerate), random.randint(20, 1000), samplerate, random.getrandbits(1)))
elif i < 65:
instruments.append(VibratoInstrument(RandNote(baseFreq, samplerate), random.randint(20, 800)))
elif i < 85:
instruments.append(BeatInstrument(RandNote(baseFreq, samplerate), random.randint(150, 1000), samplerate))
elif i < 90:
instruments.append(ChordInstrument(RandNotes(baseFreq, samplerate)))
elif i < 100:
instruments.append(RandChordInstrument(baseFreq, random.randint(2, 4), samplerate))
return instruments
def OutputToFile(file, duration):
duration = duration * 1000 if duration > 0 else 30000
baseHarmonic = random.randint(20, 200)
printDebug('opening file for output: ' + str(file))
w = wave.open(file, 'w')
w.setparams((nchannels, sampwidth, samplerate, nframes, 'NONE', 'not compressed'))
intervals = RandIntervalsFromDuration(duration)
printDebug('Generating %s intervals to fill %s ms of output: %s' % (len(intervals), duration, intervals))
instruments = RandInstruments(baseHarmonic, samplerate)
printDebug('Using %s randomly-generated "instruments": %s' % (len(instruments), instruments))
samplemgr = SampleManager(samplerate)
for i in range(len(instruments)):
printDebug('Adding instrument "%s": %s' % (i, instruments[i]))
samplemgr.addInstrument(str(i), instruments[i])
for i in intervals:
nInstruments = random.randint(1, len(instruments)) # number of instruments to use in this interval
intervalInstruments = []
for _ in range(nInstruments):
intervalInstruments.append(str(random.randint(0, len(instruments)-1)))
samplemgr.addInterval(i, intervalInstruments)
data = samplemgr.nextSample()
while data is not None:
w.writeframesraw(data)
data = samplemgr.nextFrame()
w.close()
def WriteSamplesContinuously(file, samplemgr):
data = samplemgr.nextSample()
while(True):
try:
file.writeframesraw(data)
except IOError:
pass # swallow IOError from wave.py
data = samplemgr.nextSample()
def AddIntervalsContinuously(samplemgr, instruments):
while(True):
intervals = RandIntervalsFromDuration(5000)
for i in intervals:
nInstruments = random.randint(1, len(instruments))
intervalInstruments = []
for _ in range(nInstruments):
intervalInstruments.append(str(random.randint(0, len(instruments)-1)))
samplemgr.addInterval(i, intervalInstruments)
def OutputToStdOut(duration):
if duration > 0:
OutputToFile(sys.stdout, duration)
return
baseHarmonic = random.randint(20, 200)
samplemgr = SampleManager(samplerate)
printDebug('opening stdout for endless output...')
w = wave.open(sys.stdout, 'w')
w.setparams((nchannels, sampwidth, samplerate, nframes, 'NONE', 'not compressed'))
printDebug('generating next ~20 seconds of data, using %s Hz as base frequency' % (baseHarmonic))
intervals = RandIntervalsFromDuration(20000)
printDebug('Next %s intervals: %s' % (len(intervals), intervals))
instruments = RandInstruments(baseHarmonic, samplerate)
printDebug('Generated %s fresh "instruments": %s' % (len(instruments), instruments))
for i in range(len(instruments)):
printDebug('Adding instrument "%s": %s' % (i, instruments[i]))
samplemgr.addInstrument(str(i), instruments[i])
for i in intervals:
nInstruments = random.randint(1, len(instruments))
intervalInstruments = []
for _ in range(nInstruments):
intervalInstruments.append(str(random.randint(0, len(instruments)-1)))
samplemgr.addInterval(i, intervalInstruments)
IntervalThread = threading.Thread(target=AddIntervalsContinuously, args=(samplemgr,instruments))
IntervalThread.daemon = True
IntervalThread.start()
sys.stderr.write('ready!')
WriteSamplesContinuously(w, samplemgr)
def main(**args):
global debug
debug = args['d']
filename = args['f']
duration = args['l']
if(debug):
print('Debugmode ON')
print('Output filename: "%s"' % (filename))
print('Output duration: %s seconds' % (duration))
if len(filename) > 0:
OutputToFile(filename, duration)
else:
OutputToStdOut(duration)
#--------------------#
# END MAIN PROGRAM #
#--------------------#
if __name__ == '__main__':
argparser = argparse.ArgumentParser(description='Generates semi-random WAV data')
argparser.add_argument('-f', metavar='Filename', type=str, default='', help='Output file name (will output to stdout if no file is specified)')
argparser.add_argument('-l', metavar='Length', type=int, default=-1, help='Length of file to produce, in seconds (defaults to 30 seconds for file output, infinite for stdout)')
argparser.add_argument('-d', action='store_true', help='Turns on debugging spew')
args = argparser.parse_args()
main(**vars(args))