diff options
Diffstat (limited to 'City/OrcBuilder.py')
-rwxr-xr-x | City/OrcBuilder.py | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/City/OrcBuilder.py b/City/OrcBuilder.py new file mode 100755 index 0000000..776cc9e --- /dev/null +++ b/City/OrcBuilder.py @@ -0,0 +1,342 @@ +#!/usr/bin/env python + +import csnd, sys +from CsHelpers import * + + +class Ftable: + def __init__(self): + self.start = 0 + self.num = 0 + self.size = 8192 + self.gen = 10 + self.name = "sine" + self.args = [10,1] + def varname(self): + return "gi_"+self.name + def __repr__(self): + head = [self.varname(), 'ftgen', str(self.num)+', '] + args = [str(self.start), str(self.size), str(self.gen)] + map(str, self.args) + return ' '.join(head) + ', '.join(args) + +class FtableBreakPoint(Ftable): + "constructs a Gen 2 ftable" + def __init__(self, name, *args, **num): + Ftable.__init__(self) + self.name = name + self.gen = -2 + self.size = (2**i for i in xrange(2,100) if 2**i >= len(args)).next() + self.args = args + if num.has_key('num'): self.num = num['num'] + +class FtableSample(Ftable): + "loads samples into GEN 1, and optional basepitch storage" + def __init__(self, pathname, *basepit, **num): + Ftable.__init__(self) + self.gen = -1 + filename = pathname.rpartition('/')[-1] + ndx = filename.find('.') + if ndx != -1: + self.name = removeillegals(filename[0:ndx]) + else: self.name = removeillegals(filename) + self.size = 0 + self.args = ['"'+pathname+'"', 0, 0, 0] + if len(basepit) > 0: + self.basePitch = basepit[0] + if num.has_key('num'): self.num = num['num'] + +class FtableLookup(Ftable): + def __init__(self, name, skew, *sampletables, **num): + "uses GEN17 to make an x,y lookup table suitable for mid pitch lookup. GEN17" + #skew calculates the point at which the next table is lookup up. + #pitnamedict is a dictionary of {basepitches:samplenames,...} + Ftable.__init__(self) + pitnamedict = {} + for s in sampletables: + pitnamedict[s.basePitch] = s.varname() + sorted = [] + for key in pitnamedict: + sorted.append([key, pitnamedict[key]]) + sorted.sort() + args = [] + for i in range(len(sorted)): + if i == 0: + args.append(i) + args.append(sorted[i][1]) + else: + args.append(int(round (sorted[i][0] - (sorted[i][0] - sorted[i-1][0]) * skew))) + args.append(sorted[i][1]) + self.gen = -17 + self.name = name + self.size = 128 + self.args = args + if num.has_key('num'): self.num = num['num'] + +class OrcChan: + def __init__(self, name, direction, rate, init): + self.name = name + self.direction = direction + self.rate = rate + self.initval = init + def mode(self): + return (1 if self.direction == 'input' else (2 if self.direction == 'output' else 3)) + def varname(self): + return 'g'+('a' if self.rate == 'audio' else ('k' if self.rate == 'control' else ('S' if self.rate == 'string' else 'i')))+'_'+self.name + def initline(self): + return self.varname() + '\t' + 'init' + '\t' + str(self.initval) + def __repr__(self): + final = self.varname() + ' '+'chnexport'+' \"'+self.name+'\", '+str(self.mode())+('\n'+self.initline() if (self.rate == 'audio' or self.rate == 'string') else ',2,1,0,1'+ '\n'+self.initline()) + return final + +def orcChannelMaker(insnames, parameter, direction = "input", rate = "control", init = 1): + "a simple function to generate numerous channels of the same type" + result = [] + for i in insnames: + name = i+parameter + o = OrcChan(name, direction, rate, init) + result.append(o) + return result + +#Not cognisant of scoreline parameter variables at this stage. +class OrcInstrument: + def __init__(self, name='undefined'): + self.name = name + self.lines = [] + self.effect = False #if true, then an 'always on' scoreline is automatically added. + def header(self): + return ["instr \t$"+self.name+"\n"] + def varname(self): + return "$"+self.name + def insertLine(self, line): + self.lines.append(line) + def routeOut(self, asendvar, outINS, SetLvlIns, gkvarname = 1, *chan): + "SO, gkvarname is 1 by default, but should be the gkvariable if going to mixout" + SetLvlIns.setLevel(self, outINS, gkvarname) + self.insertLine("\tMixerSend "+asendvar+", p1, "+outINS.varname()+", "+str((chan[0] if len(chan)>0 else 0))) + def routeIn(self, ainvar, *chan): + self.lines.insert(0, ainvar+" MixerReceive "+"p1,"+str((chan[0] if len(chan)>0 else 0))) + def __repr__(self): + result = self.header() + self.lines + return "\n".join(result) + '\n\nendin\n' + +class OrcSetLevelInstrument(OrcInstrument): + def __init__(self): + OrcInstrument.__init__(self) + self.name = "mixerSetLevels" + self.routemap = {} + def setLevel(self, sendINS, bussINS, gkvarname): + self.routemap[sendINS.name] = bussINS.name + self.insertLine("\t MixerSetLevel "+sendINS.varname()+", "+bussINS.varname()+", "+str(gkvarname)+"\n") #change this to the gkvariable name + def mixout(self, ainvar, *chan): + "sends output of an instrument to a mixer buss" + self.insertLine("\tMixerSend "+ainvar+", p1, "+"$output, "+str((chan[0] if len(chan)>0 else 0))) + + +class OrcMixoutInstrument(OrcInstrument): + def __init__(self): + OrcInstrument.__init__(self) + self.name = "output" + self.routeIn("am") + self.insertLine(""" + am eqfil am, 900, 200, 0.2 + a3 nreverb am, 0.12, 1, 0, 8, gi_rvbc1, 4, gi_rvba1 + a4 nreverb am, 0.12, 1, 0, 8, gi_rvbc2, 4, gi_rvba2 + a3 = am + a3*0.23 + a4 = am + a4*0.23 + outs a3, a4 + ;outs am, am + MixerClear + """) + + + +class OrcConstructor: + def __init__(self): + self.orc = "" + self.sco = "f0 28800 \n" + self.sr = 44100 + self.ksmps = 100 + self.nchnls = 2 + self.dbfs = 1 + self.macros = [] + self.ftabs = [] + self.chans = [] + self.instruments = [] + self.csline = [] + self.nameNumber = {} + self.insertftables(Ftable()) #insert a sine by default + def insertLines(self, lines): + self.csline.append(lines) + def insertInsnums(self, Instruments): + insnumgen = (i for i in xrange(1, 100)) + for i in Instruments: + num = insnumgen.next() + self.nameNumber[i.name] = num + self.macros.append("#define "+i.name+" #"+str(num)+"#") + if i.effect: + self.sco = self.sco + "i%s 0 -1 \n" %num + def insertftables(self, *tabs): + "inserts Orcfunction tables using Ftable objects" + for t in tabs: + self.ftabs.append(t) + def insertChannels(self, *OrcChans): + "inserts a Orcchannel objects into a Csound orc" + for c in OrcChans: + self.chans.append(c) + def appendInstruments(self, *CsIns): + for i in CsIns: + self.instruments.append(i) + def prependInstrument(self, CsIns): + "inserts an instrument at the front of the orchestra" + self.instruments.insert(0, CsIns) + def exportOrc(self): + self.insertInsnums(self.instruments) + header = [x+y for x,y in zip(['sr = ', 'ksmps = ', 'nchnls = ', '0dbfs = '], map(str,[self.sr, self.ksmps, self.nchnls, self.dbfs]))] + insnums = self.macros + ftabs = map(str, self.ftabs) + lines = map(str, self.csline) + chans = map(str, self.chans) + Ins = map(str, self.instruments) + result = header + insnums + ftabs + chans + lines + Ins + return '\n'.join(result) + def __repr__(self): + return "CSound orchestra object" + str(self.__dict__) + +class sndInfo: + def __init__(self, path, *csd): + "query information of an audio file at path. csd is a precompiled csound instance" + if len(csd) == 0: + cs = Csound() + self.cs = Csound.csound + else: self.cs = csd[0] #can pass a precompiled csound as an argument + self.contents = '' + args = csnd.CsoundArgVList() + args.Append('sndinfo') + args.Append('-i') + args.Append(path) + old_stdout = sys.stdout + sys.stdout = self + err = self.cs.RunUtility('sndinfo', args.argc(), args.argv()) + sys.stdout = old_stdout + self._lines() + header = [l for l in self.lines if l.__contains__('\tsrate')] + self.header = header[0].split(',') + def write(self, c): + self.contents += c + def _lines(self): + self.lines = self.contents.splitlines() + def sr(self): + srl = [c for c in self.header[0] if c.isdigit()] + return int(''.join(srl)) + def chans(self): + if self.header[1].__contains__('monaural'): + return 1 + else: return 2 + def type(self): + return self.header[2] + def duration(self): + s = self.header[3] + return float(''.join([n for n in s if n.isdigit() or n == '.'])) + def findNoteAttribute(self, attr): + result = 0 + for i in self.lines: + if i.startswith(attr, 2): + result = int(''.join([n for n in i if n.isdigit()])) + return result + def BaseNote(self): + "return the Base Note" + return self.findNoteAttribute('Base') + +def orcLoadSamples(orc, Cs, fnlookupname, *paths): + "Inserts Gen1 ftables for samples located in paths, and an associated GEN17 midi pitch lookup table based on base pitch in soundfile into orc" + sfns = [] + for p in paths: + for f in ResourceList(p,'.aif'): + snd = sndInfo(p+'/'+f, Cs.csound) + bn = snd.BaseNote() + fn = FtableSample(p+'/'+f, bn) + orc.insertftables(fn) + sfns.append(fn) + flookup = FtableLookup(fnlookupname, 0.3, *sfns) + orc.insertftables(flookup) + return flookup + +if __name__ == '__main__': + print "running OrcBuilder as __main__" + + from CsSched import * + Csynth = Csound() + TimeQueue = Sched() + + #Create a csound orchestra + orc = OrcConstructor() + orc.sr = 22050 + orc.ksmps = 256 + + #Function tables + lookuptabs = [orcLoadSamples(orc, Csynth, instr+"Lookup", eval(instr+"AudioPath")) for instr in INAMES] + ftabs = [FtableBreakPoint("rvbc1", -558, -594, -638, -678, -711, -745, -778, -808, 0.8, 0.79, 0.78, 0.77, 0.76, 0.75, 0.74, 0.73), + FtableBreakPoint("rvbc2", -517, -540, -656, -699, -752, -799, -818, -841, 0.8, 0.79, 0.78, 0.77, 0.76, 0.75, 0.74, 0.73), + FtableBreakPoint("rvba1", -278, -220, -170, -122, 0.4, 0.52, 0.64, 0.76), + FtableBreakPoint("rvba2", -333, -263, -166, -105, 0.5, 0.52, 0.64, 0.76)] + orc.insertftables(*ftabs) + + #Control Channels: Should conform to the Parameter naming convention already in use. + volumechans = orcChannelMaker(INAMES, "Volume") + timbrechans = orcChannelMaker(INAMES, "Timbre", init = 0.5) + orc.insertChannels(*volumechans) + orc.insertChannels(*timbrechans) + #timbrechans[0].varname() + + #Instruments + #first, establish a setlevel instrument + setlevels = OrcSetLevelInstrument() + setlevels.effect = True + #then work backwards. + #A mixer: + mixerout = OrcMixoutInstrument() + mixerout.effect = True + + #sampler instruments + samplerbody = """ + idur = p3 + iamp = p4 * (0dbfs / 127) + kcps init cpsmidinn(p5) + isamp table p5, %s + a1 loscil iamp, kcps, isamp + a1 dcblock a1 + kdeclick linseg 0, 0.001, 1, idur - 0.03 - 0.001, 1, 0.03, 0 + a1 = a1 * kdeclick + """ + #timbre instruments + #be aware that samplerates lower than 22050 tend to blow up the rezzy filter + #at this level of resonance. + timbrebody = (""" + idur = p3 + ires = 4.75 + kfco expcurve %s, 14 + kfco = kfco * """ + str(orc.sr*0.5 * 0.65 + 200) + """ + + a1 rezzy a1, kfco, ires + """) + + SamplerInstruments, TimbreInstruments = [OrcInstrument(i+'Sampler') for i in INAMES], [OrcInstrument(i+'Timbre') for i in INAMES] + + #it would be nice to abstract this one, but it's quite complex. Maybe later. + for Si, Ti in zip(SamplerInstruments, TimbreInstruments): + Si.insertLine(samplerbody % [x for x in [n.varname() for n in lookuptabs] if x.__contains__(Si.name[:-7])][0]) + Ti.insertLine(timbrebody % [y for y in [j.varname() for j in timbrechans] if y.__contains__(Ti.name[:-6])][0]) + Si.routeOut('a1', Ti, setlevels) + Ti.routeOut('a1', mixerout, setlevels) + Ti.routeIn('a1') + Ti.effect = True + orc.appendInstruments(Si, Ti) + + + #finally, add the mixer and level instruments + orc.appendInstruments(mixerout) + orc.prependInstrument(setlevels) + #get numbers from names: + #orc.nameNumber['BassSampler'] + print orc.exportOrc() + |