Context Free Audio

Home | Projects | Links

The Idea

These are the beginnings of a python based audio programming tool, I want to eventually have a parser that will parse syntax that is very close to what Context Free Art has for their 2D recursive image generator. So they start with basic shapes like SQAURE, CIRCLE and TRIANGLE and then you can assign attributes, and through the grammar recurse the shapes and their properties in various ways. So far I have a SINE, SAW, TRIANGLE, PULSE, NOISE generating functions with the ability to control amplitude, frequency and duration of the generated waveform. Eventually, I'd like to be able to figure out how to do the math for a filter (freq./cutoff) and other fun effects.

The Examples

Be warned that the pulse waves, saw waves and noise can be startling after a smooth sine wave. Lower your volume before listening, If you're a wimp.

Here are some examples of wave forms that can be produced. Each example steps from 60hz to 440hz in 10hz steps that are 4 seconds long each. Besides the noise sample, which is 4 seconds of random noise.

The audio files are 128kbs encoded to LAME MP3 from the generated WAV

Here's a little example of what you can do with the code I've cranked out so far. Just append this to the bottom of "The Code" and run, it takes less than a minute to render on my machine, may take longer depending on processor/memory.

for i in range(60,440,10):
    sine(i,70, .1)
for i in range(60,440,10):
    tri(i,70, .1)
for i in range(60,440,10):
    pulse(i,70, .1, 50)
for i in range(60,440,10):
    saw(i,70, .1, 0)
noise(70, 2)
output.close()

Well, I hope you understand the basis of my final vision! The code is below and uses only standard python libraries. The 'shh' function just writes \x00 to the file for the specified duration.

The Code

#! /usr/bin/python/

import struct, wave, math, random

def tri(freq,amp,dur):
    half_cycle = []
    one_cycle = []
    amp = int(float(amp)/100*clip)
    cycle_frames = out_params[2]/freq
    for i in range(0,int(cycle_frames/2)+1):
        half_cycle.append(int(i*amp/(cycle_frames/4))-amp)
    one_cycle = one_cycle + half_cycle
    half_cycle.reverse()
    one_cycle = one_cycle + half_cycle
    write(one_cycle, dur)
    return 1

def saw(freq,amp,dur,invert):
    one_cycle = []
    amp = (float(amp)/100)*clip
    cycle_frames = out_params[2]/freq
    for i in range(0,int(cycle_frames)+1):
        one_cycle.append(int(2*i*amp/cycle_frames)-amp)
    if invert == 1:
        one_cycle.reverse()
    write(one_cycle, dur)
    return 1
        
def pulse(freq,amp,dur,duty):
    one_cycle = []
    amp = (float(amp)/100)*clip
    cycle_frames = float(out_params[2]/freq)
    duty = (float(duty)/100)*cycle_frames
    for i in range(0,int(duty)):
        one_cycle.append(amp)
    for i in range(0,int(cycle_frames-duty)):
        one_cycle.append(-amp)
    write(one_cycle, dur)
    return 1
        
def sine(freq,amp,dur):
    amp = (float(amp)/100)*clip
    cycle_frames = float(out_params[2]/freq) #sample rate divided by user frequency
    const = float((2*math.pi)/cycle_frames)  #2pi radians divided by 1 cycle of frames
    deg = 0
    one_cycle = []

    for i in range(0,int(cycle_frames)):     #loops through 1 cycle of frames
        deg = deg + const
        wave_sine = int(amp*(math.sin(deg)))
        one_cycle.append(wave_sine)          #generate array of frame values
    write(one_cycle, dur)
    return 1

def noise(amp,dur):
    amp = int((float(amp)/100)*clip)
    for i in range(0,int(dur*out_params[2])):
        wave_frame = struct.pack('<h',random.randint(-amp,amp))
        output.writeframes(wave_frame)
    return 1

def shh(dur):
    for i in range(0,int(dur*out_params[2])):
        wave_frame = struct.pack('<h',0)
        output.writeframes(wave_frame)
    return 1

def write(wave, length):
    cycle_frames = len(wave)
    num_cycles = int((out_params[2]*length)/cycle_frames)
    for j in range(0,num_cycles):            #loop through num of cycles needed to fill time
        for i in wave:                       #loop through array, pack, and write
            wave_frame = struct.pack('<h',i)
            output.writeframes(wave_frame)

clip = 31000
filename = 'render'+str(random.randint(100,999))+'.wav'
output = wave.open(filename, 'w')
out_params = [1,2,44100,0,'NONE','not compressed']
output.setparams(out_params)
print filename

I'd love to hear any feedback, suggestions, questions or uses. Thanks!