#!/sw/bin/python2.6 # -*- coding: utf-8; mode: python; backup-inhibited: true -*- # # palantras.py # linear random walk through a sound file # # copyright (c) 2009 santiágo peresón # licensed under the GPL version 2. see http://www.gnu.org/licenses/gpl2.txt # # to-do: # # notes: # - when dir = -1: invert phase # - optimization: read full file, split at zero-crossings, build new sound from frags. # """ palantras.py linear random walk through a sound file usage: ./palantras.py [options] sound files should be 16-bit mono, any sample rate. options: -i, --duration= output sound duration in seconds [auto] -m, --minfrag= min fragment duration in milliseconds [100] -M, --maxfrag= max fragment duration in seconds[1000] -s, --start= start position in seconds or 'rand' [0] -p, --probs=/ forward/backward probability [7/3] -c, --count= number of files to generate [1] -d, --debug show debugging output -h, --help show this help message and exit duration: in seconds. 'auto' will walk until it finds the EOF. best used with high forward probabilities. start: can be negative. use -1 to start at EOF. count: if more than one file is specified files names will be indexed. """ from __future__ import division, print_function import aifc import getopt import os import random import sys import wave from array import array from math import copysign # fix aifc module bug aifc._skiplist += ('FLLR',) #------------------------------------------------------------------------------- # globals debug = False #------------------------------------------------------------------------------ # main def main(): """main function""" global debug # defaults duration = 'auto' minfrag = 100 maxfrag = 1000 start = '0' count = 1 fprob, bprob = 7, 3 # parse command line options try: opts, args = getopt.getopt(sys.argv[1:], 'i:m:M:s:p:c:dh', ['duration=', 'minfrag=', 'maxfrag=', 'start=', 'probs=', 'count=', 'debug', 'help']) except getopt.error, msg: optexit(2, msg) for o, a in opts: if o in ('-i', '--duration'): duration = a elif o in ('-m', '--minfrag'): minfrag = int(a) elif o in ('-M', '--maxfrag'): maxfrag = int(a) elif o in ('-s', '--start'): start = a elif o in ('-p', '--probs'): fprob, bprob = parse_probs(a) elif o in ('-c', '--count'): count = int(a) elif o in ('-h', '--help'): optexit(0) elif o in ('-d', '--debug'): debug = True if len(args) != 2: optexit(2, "ValueError: incorrect number of arguments\n") (inpath, outpath) = args # read input file dbg("reading input file: %s ...\n", inpath) srcmeta, src = read_infile(inpath) # create output files outroot, outext = os.path.splitext(outpath) dbg("creating %s output file%s ...\n", count, '' if count == 1 else 's') for i in range(1, count + 1): outpath = outpath if count == 1 else '%s-%03d%s' % (outroot, i, outext) sound_randwalk(srcmeta, src, outpath, maxdur=duration, minfrag=minfrag, maxfrag=maxfrag, start=start, fprob=fprob, bprob=bprob) dbg('done.\n') #------------------------------------------------------------------------------- # local lib def sound_randwalk(srcmeta, src, outpath, maxdur=17, minfrag=100, maxfrag=1000, start='0', fprob=7, bprob=3): """linear random walk through a sound file""" # init data # get metadata, scale times to samples, set start dbg(" initializing ...\n") (nchannels, sampwidth, framerate, nframes, comptype, compname) = srcmeta if nchannels != 1 or sampwidth != 2 or comptype != 'NONE': raise IOError("IOError: can only process 16-bit mono uncompressed files!") minfrag = int(minfrag * (framerate // 1000)) maxfrag = int(maxfrag * (framerate // 1000)) maxdur = int(float(maxdur) * framerate) if maxdur != 'auto' else -1 start = (int(float(start) * framerate) if start != 'rand' else random.randint(0, nframes - 1)) probs = [1] * fprob + [-1] * bprob outdur = 0 idx = start incr = random.choice(probs) #dbg(" minfrag: %d, maxfrag: %d, maxdur: %d, start: %d incr: %d\n", # minfrag, maxfrag, maxdur, start, incr) # create output file ofh = create_outfile(outpath) ofh.setparams((nchannels, sampwidth, framerate, 0, comptype, compname)) #dbg(" ofh: %s\n", ofh) # walk # convert data to array type, process, reconv to bytestring for writing. dbg(" processing ") try: while maxdur == -1 or outdur < maxdur: chunk = array('h') if not 0 <= idx + incr < nframes: incr *= -1 side = int(copysign(1, src[idx])) nside = int(copysign(1, src[idx + incr])) #dbg(" outdur: %d, incr: %d, val: %d, side: %d, nval: %d," # + " nside: %d\n", # outdur, incr, src[idx], side, src[idx + 1], nside) while (nside == side or len(chunk) < minfrag) \ and len(chunk) < maxfrag \ and 0 <= idx < nframes: frame = src[idx] if incr == 1 else (src[idx] * -1 - 1) chunk.append(frame) idx += incr if not 0 < idx < nframes -1: break #if idx < 0 or idx == nframes: break side = int(copysign(1, src[idx])) nside = int(copysign(1, src[idx + incr])) dbg('.') #dbg(str(chunk) + '\n') chunk.byteswap() ofh.writeframesraw(chunk.tostring()) outdur += len(chunk) if idx == nframes - 1 and maxdur == -1: break if len(chunk) == maxfrag: incr *= -1 else: incr = random.choice(probs) #import pdb; pdb.set_trace() dbg('\n') finally: ofh.close() return def read_infile(inpath): """read a wave or aiff file""" root, ext = os.path.splitext(inpath) ifh = aifc.open(inpath, 'rb') if ext != '.wav' else wave.open(inpath, 'rb') src = array('h', ifh.readframes(ifh.getnframes())) src.byteswap() meta = ifh.getparams() ifh.close() return (meta, src) def create_outfile(outpath): """open a wave or aiff file for writing""" dbg(" creating output file: %s ...\n", outpath) if os.path.exists(outpath): raise IOError("IOError: output file exists") root, ext = os.path.splitext(outpath) if ext != '.wav': ofh = aifc.open(outpath, 'wb') ofh.aiff() else: ofh = wave.open(outpath, 'wb') return ofh def parse_probs(arg): """parse probs argument""" try: fstr, bstr = arg.split('/') fprob, bprob = int(fstr), int(bstr) except: raise ValueError("ValueError: can't parse probabilities argument!") return (fprob, bprob) def optexit(status, pre=''): """getopt err/help exit function""" if pre: print(pre, file=sys.stderr) print(__doc__.strip(), file=sys.stderr) sys.exit(status) def dbg(text, *args, **kwargs): """print debugging output to stderr""" global debug if debug: if kwargs: text = text % kwargs elif args: text = text % args print(text, file=sys.stderr, end='') sys.stderr.flush() #------------------------------------------------------------------------------ if __name__ == "__main__": main()