"""
Filter Effects
--------------
Effects are added to Player objects as keywords instructions like `dur`
or `amp` but are a little more tricky. Each effect has a "title" keyword,
which requires a nonzero value to add the effect to a Player. Effects
also have other attribute keywords which can be any value and may have
a default value which is set when a Player is created.
::
# Example. Reverb effect "title" is `room` and attribute is `mix`, which
# defaults to 0.25. The following adds a reverb effect
p1 >> pads(room=0.5)
# This still adds the effect, but a mix of 0 doesn't actually do anything
p1 >> pads(room=0.5, mix=0)
# This effect is not added as the "title" keyword, room, is 0
p1 >> pads(room=0, mix=0.5)
Other effects are outlined below:
*High Pass Filter* - Title keyword: `hpf`, Attribute keyword(s): `hpr`
Only frequences **above** the value of `hpf` are kept in the final signal. Use `hpr` to set the resonance (usually a value between 0 and 1)
*Low Pass Filter* - Title keyword: `lpf`, Attribute keyword(s): `lpr`
Only frequences **below** the value of `lpf` are kept in final signal. Use `lpr` to set the resonance (usually a value between 0 and 1)
*Bitcrush* - Title keyword: `bits`, Attribute keyword(s): `crush`
The bit depth, in number of `bits`, that the signal is reduced to; this is a value between 1 and 24 where other values are ignored. Use `crush` to set the amount of reduction to the bitrate (defaults to 8)
*Reverb* - Title keyword: `room`, Attribute keyword(s): `mix`
The `room` argument specifies the size of the room and `mix` is the dry/wet mix of reverb; this should be a value between 0 and 1 (defalts to 0.25)
*Chop* - Title keyword: `chop`, Attribute keyword(s): `sus`
'Chops' the signal into chunks using a low frequency pulse wave over the sustain of a note.
*Slide To* - Title keyword: `slide`, Attribute keyword(s):
Slides' the frequency value of a signal to `freq * (slide+1)` over the duration of a note (defaults to 0)
*Slide From* - Title keyword: `slidefrom`, Attribute keyword(s):
Slides' the frequency value of a signal from `freq * (slidefrom)` over the duration of a note (defaults to 1)
*Comb delay (echo)* - Title keyword: `echo`, Attribute keyword(s): `decay`
Sets the decay time for any echo effect in beats, works best on Sample Player (defaults to 0)
*Panning* - Title keyword: `pan`, Attribute keyword(s):
Panning, where -1 is far left, 1 is far right (defaults to 0)
*Vibrato* - Title keyword: `vib`, Attribute keyword(s):
Vibrato (defaults to 0)
Undocumented: Spin, Shape, Formant, BandPassFilter, Echo
"""
from __future__ import absolute_import, division, print_function
import os.path
from ..Settings import EFFECTS_DIR, SC3_PLUGINS
from ..ServerManager import Server
[docs]
class Effect:
server=Server
def __init__(self, foxdot_name, synthdef, args={}, control=False):
self.name = foxdot_name
self.synthdef = synthdef
self.filename = EFFECTS_DIR + "/{}.scd".format(self.synthdef)
self.args = args.keys()
self.vars = ["osc"]
self.defaults = args
self.effects = []
self.control = control
self.suffix = "kr" if self.control else "ar"
self.channels = 1 if self.control else 2
self.input = "osc = In.{}(bus, {});\n".format(self.suffix, self.channels)
self.output = "ReplaceOut.{}".format(self.suffix)
[docs]
@classmethod
def set_server(cls, server):
cls.server = server
def __repr__(self):
# return "<Fx '{}' -- args: {}>".format(self.synthdef, ", ".join(self.args))
other_args = ['{}'.format(arg) for arg in self.args if arg != self.name]
other_args = ", other args={}".format(other_args) if other_args else ""
return "<'{}': keyword='{}'{}>".format(self.synthdef, self.name, other_args)
def __str__(self):
s = "SynthDef.new(\\{},\n".format(self.synthdef)
s += "{" + "|bus, {}|\n".format(", ".join(self.args))
s += "var {};\n".format(",".join(self.vars))
s += self.input
s += self.list_effects()
s += self.output
s += "(bus, osc)}).add;"
return s
[docs]
def add(self, string):
self.effects.append(string)
return
[docs]
def doc(self, string):
""" Set a docstring for the effects"""
return
[docs]
def list_effects(self):
s = ""
for p in self.effects:
s += p + ";\n"
return s
[docs]
def add_var(self, name):
if name not in self.vars:
self.vars.append(name)
return
[docs]
def save(self):
''' writes to file and sends to server '''
# 1. See if the file exists
if os.path.isfile(self.filename):
with open(self.filename) as f:
contents = f.read()
else:
contents = ""
# 2. If it does, check contents
this_string = self.__str__()
if contents != this_string:
try:
with open(self.filename, 'w') as f:
f.write(this_string)
except IOError:
print("IOError: Unable to update '{}' effect.".format(self.synthdef))
# 3. Send to server
self.load()
[docs]
def load(self):
""" Load the Effect """
if self.server is not None:
self.server.loadSynthDef(self.filename)
return
[docs]
class In(Effect):
def __init__(self):
Effect.__init__(self, 'startSound', 'startSound')
self.save()
def __str__(self):
s = "SynthDef.new(\\startSound,\n"
s += "{ arg bus, rate=1, sus; var osc;\n"
s += " ReplaceOut.kr(bus, rate)}).add;\n"
return s
[docs]
class Out(Effect):
def __init__(self):
self.max_duration = 8
Effect.__init__(self, 'makeSound', 'makeSound')
self.save()
def __str__(self):
s = "SynthDef.new(\\makeSound,\n"
s += "{ arg bus, sus; var osc;\n"
s += " osc = In.ar(bus, 2);\n"
s += " osc = EnvGen.ar(Env([1,1,0],[sus * {}, 0.1]), doneAction: 14) * osc;\n".format(self.max_duration)
s += " DetectSilence.ar(osc, amp:0.0001, time: 0.1, doneAction: 14);\n"
#s += " Out.ar(0, osc);\n"
s += "OffsetOut.ar(0, osc[0]);\n"
s += "OffsetOut.ar(1, osc[1]);\n"
s += " }).add;\n"
return s
[docs]
class EffectManager(dict):
def __init__(self):
dict.__init__(self)
self.kw=[]
self.all_kw=[]
self.defaults={}
self.order={N:[] for N in range(3)}
def __repr__(self):
return "\n".join([repr(value) for value in self.values()])
[docs]
def values(self):
return [self[key] for key in self.sort_by("synthdef")]
[docs]
def sort_by(self, attr):
""" Returns the keys sorted by attribute name"""
return sorted(self.keys(), key=lambda effect: getattr(self[effect], attr))
[docs]
def new(self, foxdot_arg_name, synthdef, args, order=2):
self[foxdot_arg_name] = Effect(foxdot_arg_name, synthdef, args, order==0)
if order in self.order:
self.order[order].append(foxdot_arg_name)
else:
self.order[order] = [foxdot_arg_name]
# Store the main keywords together
self.kw.append(foxdot_arg_name)
# Store other sub-keys
for arg in args:
if arg not in self.all_kw:
self.all_kw.append(arg)
# Store the default value
self.defaults[arg] = args[arg]
return self[foxdot_arg_name]
[docs]
def kwargs(self):
""" Returns the title keywords for each effect """
return tuple(self.kw)
[docs]
def all_kwargs(self):
""" Returns *all* keywords for all effects """
return tuple(self.all_kw)
def __iter__(self):
for key in self.kw:
yield key, self[key]
[docs]
def reload(self):
""" Re-sends each effect to SC """
for kw, effect in self:
effect.load()
In(); Out();
return
# -- TODO
# Have ordered effects e.g.
# 0. Process frequency / playback rate
# 1. Before envelope
# 2. Adding the envelope
# 3. After envelope
FxList = EffectManager()
Effects = FxList # Alias - to become default
# Frequency Effects
fx = FxList.new("vib", "vibrato", {"vib": 0, "vibdepth": 0.02}, order=0)
fx.add("osc = Vibrato.ar(osc, vib, depth: vibdepth)")
fx.save()
fx = FxList.new("slide", "slideTo", {"slide":0, "sus":1, "slidedelay": 0}, order = 0)
fx.add("osc = osc * EnvGen.ar(Env([1, 1, slide + 1], [sus*slidedelay, sus*(1-slidedelay)]))")
fx.save()
fx = FxList.new("slidefrom", "slideFrom", {"slidefrom": 0, "sus": 1, "slidedelay": 0}, order=0)
fx.add("osc = osc * EnvGen.ar(Env([slidefrom + 1, slidefrom + 1, 1], [sus*slidedelay, sus*(1-slidedelay)]))")
fx.save()
fx = FxList.new("glide", "glissando", {"glide": 0, "glidedelay": 0.5, "sus": 1}, order=0)
fx.add("osc = osc * EnvGen.ar(Env([1, 1, (1.059463**glide)], [sus*glidedelay, sus*(1-glidedelay)]))")
fx.save()
fx = FxList.new("bend", "pitchBend", {"bend": 0, "sus": 1, "benddelay": 0}, order=0)
fx.add("osc = osc * EnvGen.ar(Env([1, 1, 1 + bend, 1], [sus * benddelay, (sus*(1-benddelay)/2), (sus*(1-benddelay)/2)]))")
fx.save()
fx = FxList.new("coarse", "coarse", {"coarse": 0, "sus": 1}, order=0)
fx.add("osc = osc * LFPulse.ar(coarse / sus)")
fx.save()
fx = FxList.new("striate", "striate", {"striate": 0, "sus": 1, "buf": 0, "rate": 1}, order=0)
fx.add("rate = (BufDur.kr(buf) / sus)")
fx.add("rate = Select.kr(rate > 1, [1, rate])")
fx.add("osc = osc * LFPulse.ar(striate / sus, width: (BufDur.kr(buf) / rate) / sus) * rate")
fx.save()
fx = FxList.new("pshift", "pitchShift", {"pshift":0}, order=0)
fx.add("osc = osc * (1.059463**pshift)")
fx.save()
# fx = FxList.new("fm_sin", "FrequencyModulationSine", {"fm_sin":0, "fm_sin_i":1}, order=0)
# fx.add("osc = osc + (fm_sin_i * SinOsc.kr(osc * fm_sin))")
# fx.save()
# fx = FxList.new("fm_saw", "FrequencyModulationSaw", {"fm_saw":0, "fm_saw_i":1}, order=0)
# fx.add("osc = osc + (fm_saw_i * Saw.kr(osc * fm_saw))")
# fx.save()
# fx = FxList.new("fm_pulse", "FrequencyModulationPulse", {"fm_pulse":0, "fm_pulse_i":1}, order=0)
# fx.add("osc = osc + (fm_pulse_i * Pulse.kr(osc * fm_pulse))")
# fx.save()
# Signal effects
fx = FxList.new('hpf','highPassFilter', {'hpf': 0, 'hpr': 1}, order=2)
fx.doc("Highpass filter")
fx.add('osc = RHPF.ar(osc, hpf, hpr)')
fx.save()
fx = FxList.new('lpf','lowPassFilter', {'lpf': 0, 'lpr': 1}, order=2)
fx.add('osc = RLPF.ar(osc, lpf, lpr)')
fx.save()
fx = FxList.new('swell','filterSwell', {'swell': 0, 'sus': 1, 'hpr': 1}, order=2)
fx.add_var("env")
fx.add("env = EnvGen.kr(Env([0,1,0], times:[(sus*0.25), (sus*0.25)], curve:\\sin))")
fx.add('osc = RHPF.ar(osc, env * swell * 2000, hpr)')
fx.save()
fx = FxList.new("bpf", "bandPassFilter", {"bpf": 0, "bpr": 1, "bpnoise": 0, "sus": 1}, order=2)
fx.add("bpnoise = bpnoise / sus")
fx.add("bpf = LFNoise1.kr(bpnoise).exprange(bpf * 0.5, bpf * 2)")
fx.add("bpr = LFNoise1.kr(bpnoise).exprange(bpr * 0.5, bpr * 2)")
fx.add("osc = BPF.ar(osc, bpf, bpr)")
fx.save()
if SC3_PLUGINS:
fx = FxList.new('crush', 'bitcrush', {'bits': 8, 'sus': 1, 'amp': 1, 'crush': 0}, order=1)
fx.add("osc = Decimator.ar(osc, rate: 44100/crush, bits: bits)")
fx.add("osc = osc * Line.ar(amp * 0.85, 0.0001, sus * 2)")
fx.save()
fx = FxList.new('dist', 'distortion', {'dist': 0, 'tmp': 0}, order=1)
fx.add("tmp = osc")
fx.add("osc = CrossoverDistortion.ar(osc, amp:0.2, smooth:0.01)")
fx.add("osc = osc + (0.1 * dist * DynKlank.ar(`[[60,61,240,3000 + SinOsc.ar(62,mul:100)],nil,[0.1, 0.1, 0.05, 0.01]], osc))")
fx.add("osc = (osc.cubed * 8).softclip * 0.5")
fx.add("osc = SelectX.ar(dist, [tmp, osc])")
fx.save()
# Envelope -- just include in the SynthDef and use asdr?
# Post envelope effects
fx = FxList.new('chop', 'chop', {'chop': 0, 'sus': 1}, order=2)
fx.add("osc = osc * LFPulse.kr(chop / sus, add: 0.01)")
fx.save()
fx = FxList.new('tremolo', 'tremolo', {'tremolo': 0, 'beat_dur': 1}, order=2)
fx.add("osc = osc * SinOsc.ar( tremolo / beat_dur, mul:0.5, add:0.5)")
fx.save()
fx = FxList.new('echo', 'combDelay', {'echo': 0, 'beat_dur': 1, 'echotime': 1}, order=2)
fx.add('osc = osc + CombL.ar(osc, delaytime: echo * beat_dur, maxdelaytime: 2 * beat_dur, decaytime: echotime * beat_dur)')
fx.save()
fx = FxList.new('spin', 'spinPan', {'spin': 0,'sus': 1}, order=2)
fx.add('osc = osc * [FSinOsc.ar(spin / 2, iphase: 1, mul: 0.5, add: 0.5), FSinOsc.ar(spin / 2, iphase: 3, mul: 0.5, add: 0.5)]')
fx.save()
fx = FxList.new("cut", "trimLength", {"cut": 0, "sus": 1}, order=2)
fx.add("osc = osc * EnvGen.ar(Env(levels: [1,1,0.01], curve: 'step', times: [sus * cut, 0.01]))")
fx.save()
fx = FxList.new('room', 'reverb', {'room': 0, 'mix': 0.1}, order=2)
fx.add("osc = FreeVerb.ar(osc, mix, room)")
fx.save()
fx = FxList.new("formant", "formantFilter", {"formant": 0}, order=2)
fx.add("formant = (formant % 8) + 1")
fx.add("osc = Formlet.ar(osc, formant * 200, ((formant % 5 + 1)) / 1000, (formant * 1.5) / 600).tanh")
fx.save()
fx = FxList.new("shape", "wavesShapeDistortion", {"shape":0}, order=2)
fx.add("osc = (osc * (shape * 50)).fold2(1).distort / 5")
fx.save()
fx = FxList.new("drive", "overdriveDistortion", {"drive":0}, order=2)
fx.add("osc = (osc * (drive * 50)).clip(0,0.2).fold2(2)")
fx.save()
In(); Out()
Effect.server.setFx(FxList)