#!/usr/bin/env python ''' Franz is an auditory stack-based programming language, named for Franz Ferdinand [the band]. Since auditory file processing is not standardized in python, this script requires raw audio files, a *nix platform, and Numpy. Functions to generate raw audio files are included, as well as a function to pipe the data to /dev/dsp, so you can hear your program. ''' try: import stacker except: print 'you need stacker' raise SystemExit try: import Numeric except: print 'you need NumPy' print 'http://numpy.scipy.org' raise SystemExit import os, sys, threading from math import sin, cos, asin, acos, pi try: from matplotlib import pylab def plot(*funcs, **kw): if len(funcs) == 0: print "example usage:\n plot('t', lambda x: x**3, 'x+x**3', min=-10, max=10, step=.1, style='r-')" else: min = kw.get('min', -10) max = kw.get('max', 10) step = kw.get('step', .1) sty = kw.get('style', kw.get('sty', '')) for f in funcs: x = map((lambda x:x*step), range(min/step, max/step)) if isinstance(f, str): pylab.plot(x, map(lambda x_: eval(f, {'x':x_, 't':x_}), x), sty) elif isinstance(f, list) and all([isinstance(x, (int, float, long, complex)) for x in f]): pylab.plot(f, sty) else: pylab.plot(x, map(f, x), sty) pylab.show() except: # get matplotlib if you want to be able to plot stuff # http://sourceforge.net/projects/matplotlib pass try: import psyco psyco.full() except ImportError: pass try: any and all except: def any(s): for x in s: if x: return True return False def all(s): for x in s: if not x: return False return True HZ = 60 HZ_i = (1.0 / HZ) SAMPLE_FREQ = 1000 SAMPLE_FREQ_i = (1.0 / SAMPLE_FREQ) FRAME_LEN = SAMPLE_FREQ # number of bytes per "frame" def frangex(start=None, end=None, step=None): ''' The builtin xrange doesn't support floats, so you either need to divide all the elements of xrange by some number, or use this generator! Examples: for t in frangex(0, 10, 1./440): print math.cos(t) for t in frangex(-10, 10, 1./1000): print math.sin(t) ''' if start is None: start = 10 if end is None: if step is None: step = 1.0 end = start start = 0.0 if step is None: step = 1.0 while cmp(step, 0) == cmp(end, start) != 0: yield start start += step def gen_tone(octave, semitone=0): ''' 440 == gen_tone(4) ''' return 440 * (2 ** ((octave - 4) + (semitone / 12.0))) def gen_raw_frame(freq, length=1): ''' return a string of length FRAME_LEN * length * SAMPLE_FREQ suitable for passing to play_raw. ''' freq *= 2 * pi res = '' for i in frangex(0, length, SAMPLE_FREQ_i): res += chr(127 + int(127 * cos(freq * i))) return res def gen_raw(frame_specs): ''' gen_raw([(440, 1), (220, 2)]) == gen_raw_frame(440, 1) + gen_raw_frame(220, 2) ''' res = '' for spec in frame_specs: if isinstance(spec, (list, tuple)): res += gen_raw_frame(*spec) else: res += gen_raw_frame(spec) return res TONES = [ gen_tone(octave, semitone) for octave in xrange(4) for semitone in xrange(12) ] TONES.sort() TONES_FRAMES = [ gen_raw_frame(tone, 1) for tone in TONES ] TONES_FRAMES_numpy = [ Numeric.array([ord(c) for c in frame]) for frame in TONES_FRAMES ] N_TONES = len(TONES) try: del tone, octave, semitone except: pass def closest_tone_n(raw): ''' return the index of the tone in TONES that is nearest to the dominant frequency in the raw sample. ''' raw = Numeric.array([ord(c) for c in raw]) scores = [ max(Numeric.convolve(frame, raw)) for frame in TONES_FRAMES_numpy ] return scores.index(max(scores)) def closest_tone_ns(raw): return [ closest_tone_n(raw[frame_n : (frame_n + FRAME_LEN)]) for frame_n in xrange(0, (len(raw) + 1), FRAME_LEN) ] def closest_tone(raw): ''' return approximately the dominant tone frequency in the raw sample. ''' return TONES[closes_tone_n(raw)] def dsp(mode='w'): ''' open /dev/dsp in mode mode ''' return file('/dev/dsp', mode) def play_raw(raw): ''' write raw straight to dsp() ''' dsp().write(raw) play = play_raw class Translator: ''' ''' def __init__(self, raw): self.ops = [] self.values = [] self.value = lambda n: self.values[n] self.get = lambda n: self.ops[n] prev_tone_n = -1 n_prev_tones = 0 op_n = 0 for tone_n in closest_tone_ns(raw): print '\n\t' % tone_n if tone_n == prev_tone_n: n_prev_tones += 1 elif prev_tone_n == -1: prev_tone_n = tone_n n_prev_tones = 1 else: self.ops.append(prev_tone_n) self.values.append(n_prev_tones) prev_tone_n = tone_n n_prev_tones = 1 op_n += 1 # end __init__ @staticmethod def from_asm(asm): ''' convert an assembly-ish representation to a franz song-program. ''' ops = [op.func_name for op in Interpreter.ops] st = stacker.Translator(asm) raw = '' prev_tkn = None for i, tkn in enumerate(st.tkns): if tkn == 'push': if prev_tkn == 'push': raw += TONES_FRAMES[ops.index('nop')] raw += gen_raw_frame(TONES[ops.index(tkn)], st.value(i)) else: raw += TONES_FRAMES[ops.index(tkn)] prev_tkn = tkn return raw def to_asm(self): ''' ''' asm = '' for i, op in enumerate(self.ops): op = Interpreter.ops[op].func_name if op == 'push': asm += str(self.values[i]) else: asm += op asm += ' ' return asm[:-1] @staticmethod def test(): for raw in [Translator.HELLO, Translator.FIBONACCI]: Interpreter(raw=raw)() class Interpreter(stacker.Interpreter): Translator = Translator def print_tone(self): play_raw(gen_raw_frame(self.stack.pop())) def print_tone2(self): play_raw(gen_raw_frame(self.stack.pop(), self.stack.pop())) def read_tone(self): self.stack.append(closest_tone_n(dsp('r').read(FRAME_LEN))) ops = stacker.Interpreter.max_ops_list ops.insert(-2, print_tone) ops.insert(-2, print_tone2) ops.append(read_tone) Translator.HELLO = Translator.from_asm(stacker.Translator.HELLO) Translator.FIBONACCI = Translator.from_asm(stacker.Translator.FIBONACCI) Translator.FRANZ = Translator.from_asm('read_tone eval 0 jump') test = Translator.test stacker.magic(interpreter=Interpreter)