From fc1bcc55cd9cc41de02913215077d27fe6cdc567 Mon Sep 17 00:00:00 2001 From: vvk_mhn Date: Tue, 4 Feb 2025 12:27:47 +0530 Subject: [PATCH 1/3] Added stereo overdrive ugen and test function in init --- apps/test/allugens.srp | 557 +++++++++------------------------ apps/test/dspmanifest.txt | 1 + apps/test/init.srp | 18 ++ ugens/overdrive/README.md | 26 ++ ugens/overdrive/overdrive.cpp | 178 +++++++++++ ugens/overdrive/overdrive.h | 242 ++++++++++++++ ugens/overdrive/overdrive.srp | 33 ++ ugens/overdrive/overdrive.ugen | 23 ++ 8 files changed, 671 insertions(+), 407 deletions(-) create mode 100644 ugens/overdrive/README.md create mode 100644 ugens/overdrive/overdrive.cpp create mode 100644 ugens/overdrive/overdrive.h create mode 100644 ugens/overdrive/overdrive.srp create mode 100644 ugens/overdrive/overdrive.ugen diff --git a/apps/test/allugens.srp b/apps/test/allugens.srp index b6d80b5..4ce79d6 100644 --- a/apps/test/allugens.srp +++ b/apps/test/allugens.srp @@ -1,6 +1,5 @@ - -# ---- included from /Users/rbd/arco/serpent/srp/vu.srp ---- - + + # vu.srp -- record and play audio in memory # # Roger B. Dannenberg @@ -25,10 +24,9 @@ class Vu (Ugen): display "Vu set", id, value.id o2_send_cmd("/arco/vu/repl_input", 0, "UU", id, value.id) this - - -# ---- included from /Users/rbd/arco/serpent/srp/probe.srp ---- - + + + # probe.srp -- stream audio from file # # Roger B. Dannenberg @@ -67,10 +65,9 @@ class Probe (Ugen): running = false this - - -# ---- included from /Users/rbd/arco/serpent/srp/pwl.srp ---- - + + + # pwl.srp -- piece-wise linear envelope # # Roger B. Dannenberg @@ -92,10 +89,9 @@ class Pwlb (Envelope): def pwlb(keyword init, start = true, rest points): envelope(Pwlb(points), init, start) - - -# ---- included from /Users/rbd/arco/serpent/srp/pwe.srp ---- - + + + # pwe.srp -- piece-wise exponential envelope # # Roger B. Dannenberg @@ -117,113 +113,9 @@ class Pweb (Envelope): def pweb(keyword init, start = true, lin = false, rest points): envelope(Pweb(points), init, start, lin) - - -# ---- included from /Users/rbd/arco/serpent/srp/unaryugen.srp ---- - -# unaryugen.srp - constructor implementation for abs, neg, exp, log, -# log10, log2, sqrt, step_to_hz, hz_to_step, vel_to_lin, lin_to_vel. -# For completeness, you can create a Unary as well with ugen_unary. - -UNARY_OP_ABS = 0 -UNARY_OP_NEG = 1 -UNARY_OP_EXP = 2 -UNARY_OP_LOG = 3 -UNARY_OP_LOG10 = 4 -UNARY_OP_LOG2 = 5 -UNARY_OP_SQRT = 6 -UNARY_OP_STEP_TO_HZ = 7 -UNARY_OP_HZ_TO_STEP = 8 -UNARY_OP_VEL_TO_LINEAR = 9 -UNARY_OP_LINEAR_TO_VEL = 10 -UNARY_OP_DB_TO_LINEAR = 11 -UNARY_OP_LINEAR_TO_DB = 12 - -def unary(op, x1, optional chans): - if not chans: - chans = max_chans(1, x1) - Ugen(create_ugen_id(), "unary", chans, 'a', "iU", - 'op', op, 'x1', x1) - - -def unaryb(op, x1, optional chans): - if not isnumber(x1) and x1.rate != 'b': - display "ERROR: 'x1' input to Ugen 'mathb' must be block rate", op - return nil - if not chans: - chans = max_chans(1, x1) - Ugen(create_ugen_id(), "unaryb", chans, 'b', "iU", - 'op', op, 'x1', x1) - - -def ugen_abs(x1, optional chans): - unary(UNARY_OP_ABS, x1, chans) -def ugen_absb(x1, optional chans): - unaryb(UNARY_OP_ABS, x1, chans) - -def ugen_neg(x1, optional chans): - unary(UNARY_OP_NEG, x1, chans) -def ugen_negb(x1, optional chans): - unaryb(UNARY_OP_NEG, x1, chans) - -def ugen_exp(x1, optional chans): - unary(UNARY_OP_EXP, x1, chans) -def ugen_expb(x1, optional chans): - unaryb(UNARY_OP_EXP, x1, chans) - -def ugen_log(x1, optional chans): - unary(UNARY_OP_LOG, x1, chans) -def ugen_logb(x1, optional chans): - unaryb(UNARY_OP_LOG, x1, chans) - -def ugen_log10(x1, optional chans): - unary(UNARY_OP_LOG10, x1, chans) -def ugen_log10b(x1, optional chans): - unaryb(UNARY_OP_LOG10, x1, chans) - -def ugen_log2(x1, optional chans): - unary(UNARY_OP_LOG2, x1, chans) -def ugen_log2b(x1, optional chans): - unaryb(UNARY_OP_LOG2, x1, chans) - -def ugen_sqrt(x1, optional chans): - unary(UNARY_OP_SQRT, x1, chans) -def ugen_sqrtb(x1, optional chans): - unaryb(UNARY_OP_SQRT, x1, chans) - -def ugen_step_to_hz(x1, optional chans): - unary(UNARY_OP_STEP_TO_HZ, x1, chans) -def ugen_step_to_hzb(x1, optional chans): - unaryb(UNARY_OP_STEP_TO_HZ, x1, chans) - -def ugen_hz_to_step(x1, optional chans): - unary(UNARY_OP_HZ_TO_STEP, x1, chans) -def ugen_hz_to_stepb(x1, optional chans): - unaryb(UNARY_OP_HZ_TO_STEP, x1, chans) - -def ugen_vel_to_linear(x1, optional chans): - unary(UNARY_OP_VEL_TO_LINEAR, x1, chans) -def ugen_vel_to_linearb(x1, optional chans): - unaryb(UNARY_OP_VEL_TO_LINEAR, x1, chans) - -def ugen_linear_to_vel(x1, optional chans): - unary(UNARY_OP_LINEAR_TO_VEL, x1, chans) -def ugen_linear_to_velb(x1, optional chans): - unaryb(UNARY_OP_LINEAR_TO_VEL, x1, chans) - -def ugen_db_to_linear(x1, optional chans): - unary(UNARY_OP_DB_TO_LINEAR, x1, chans) -def ugen_db_to_linearb(x1, optional chans): - unaryb(UNARY_OP_DB_TO_LINEAR, x1, chans) - -def ugen_linear_to_db(x1, optional chans): - unary(UNARY_OP_LINEAR_TO_DB, x1, chans) -def ugen_linear_to_dbb(x1, optional chans): - unaryb(UNARY_OP_LINEAR_TO_DB, x1, chans) - - -# ---- included from /Users/rbd/arco/serpent/srp/mix.srp ---- - + + + # mix.srp -- audio mixer # # Roger B. Dannenberg @@ -334,10 +226,9 @@ def mix_name(i): # handy function to convert index into a symbol to name an input when # inputs are originally created in a loop or come from an array intern("in" + str(i)) - - -# ---- included from /Users/rbd/arco/ugens/sine/sine.srp ---- - + + + # sine.srp - constructor implementation # # (machine generated by u2f.py) @@ -358,10 +249,9 @@ def sineb(freq, amp, optional chans): chans = max_chans(max_chans(1, freq), amp) Ugen(create_ugen_id(), "sineb", chans, 'b', "UU", 'freq', freq, 'amp', amp) - - -# ---- included from /Users/rbd/arco/serpent/srp/mathugen.srp ---- - + + + # mathugen.srp - constructor implementation for mult, add, ugen_div, ugen_max, # ugen_min, ugen_clip, ugen_less, ugen_greater, ugen_soft_clip. # For completeness, you can create a Math as well with ugen_math. @@ -527,10 +417,9 @@ def ugen_cos(x1, x2, optional chans): Math(MATH_OP_COS, x1, x2, chans) def ugen_cosb(x1, x2, optional chans): Mathb(MATH_OP_COS, x1, x2, chans) - - -# ---- included from /Users/rbd/arco/ugens/reson/reson.srp ---- - + + + # reson.srp - constructor implementation # # (machine generated by u2f.py) @@ -559,10 +448,9 @@ def resonb(input, center, q, optional chans): Ugen(create_ugen_id(), "resonb", chans, 'b', "UUU", 'input', input, 'center', center, 'q', q) - - -# ---- included from /Users/rbd/arco/ugens/lowpass/lowpass.srp ---- - + + + # lowpass.srp - constructor implementation # # (machine generated by u2f.py) @@ -588,10 +476,9 @@ def lowpassb(input, cutoff, optional chans): Ugen(create_ugen_id(), "lowpassb", chans, 'b', "UU", 'input', input, 'cutoff', cutoff) - - -# ---- included from /Users/rbd/arco/serpent/srp/fileplay.srp ---- - + + + # fileplay.srp -- stream audio from file # # Roger B. Dannenberg @@ -618,10 +505,9 @@ class Fileplay (Ugen): def stop(): go(false) this - - -# ---- included from /Users/rbd/arco/serpent/srp/filerec.srp ---- - + + + # filerec.srp -- stream audio from file # # Roger B. Dannenberg @@ -645,10 +531,9 @@ class Filerec (Ugen): def stop(): go(false) this - - -# ---- included from /Users/rbd/arco/serpent/srp/recplay.srp ---- - + + + # recplay.srp -- record and play audio in memory # # Roger B. Dannenberg @@ -686,10 +571,9 @@ class Recplay (Ugen): def borrow(u): o2_send_cmd("/arco/recplay/borrow", 0, "UU", id, u.id) this - - -# ---- included from /Users/rbd/arco/serpent/srp/delay.srp ---- - + + + # delay.srp -- feedback delay unit generator # # Roger B. Dannenberg @@ -705,10 +589,9 @@ def delay(input, dur, fb, maxdur, optional chans = 1): Delay(chans, input, dur, fb, maxdur) # delay as a function - - -# ---- included from /Users/rbd/arco/serpent/srp/allpass.srp ---- - + + + # allpass.srp -- feedback all-pass filter unit generator # # Roger B. Dannenberg @@ -724,10 +607,9 @@ def allpass(input, dur, fb, maxdur, optional chans = 1): Allpass(chans, input, dur, fb, maxdur) # allpass as a function - - -# ---- included from /Users/rbd/arco/serpent/srp/olapitchshift.srp ---- - + + + # olapitchshift.srp -- overlap add pitch shift unit generator # # Roger B. Dannenberg @@ -746,10 +628,9 @@ class Ola_pitch_shift (Ugen): def ola_pitch_shift(input, ratio, xfade, windur, optional chans = 1): Ola_pitch_shift(chans, input, ratio, xfade, windur) - - -# ---- included from /Users/rbd/arco/serpent/srp/feedback.srp ---- - + + + # feedback.srp -- mix input with most recent output of some Ugen # # Roger B. Dannenberg @@ -769,10 +650,9 @@ class Feedback (Ugen): def feedback(input, from, gain, optional chans = 1): Feedback(chans, input, from, gain) - - -# ---- included from /Users/rbd/arco/serpent/srp/granstream.srp ---- - + + + # granstream.srp -- granular synthesis from input stream unit generator # # Roger B. Dannenberg @@ -829,10 +709,9 @@ def granstream(input, polyphony, dur, enable, optional chans = 1): Granstream(chans, input, polyphony, dur, enable) # granstream as a function - - -# ---- included from /Users/rbd/arco/serpent/srp/flsyn.srp ---- - + + + # flsyn.srp - fluid synth interface # # Roger B. Dannenberg @@ -882,10 +761,9 @@ class Flsyn (Ugen): def flsyn(path): Flsyn(path) - - -# ---- included from /Users/rbd/arco/serpent/srp/pv.srp ---- - + + + # pv.srp -- overlap add pitch shift unit generator # # Roger B. Dannenberg @@ -908,10 +786,9 @@ class Pv(Ugen): def pv(input, ratio, fftsize, hopsize, points, mode, optional chans = 1): Pv(chans, input, ratio, fftsize, hopsize, points, mode) - - -# ---- included from /Users/rbd/arco/serpent/srp/yin.srp ---- - + + + # yin.srp -- yin pitch estimation unit generator # # Roger B. Dannenberg @@ -926,10 +803,9 @@ class Yin(Ugen): def yin(input, minstep, maxstep, hopsize, address, optional chans = 1): Yin(chans, input, minstep, maxstep, hopsize, address) # yin as a function - - -# ---- included from /Users/rbd/arco/serpent/srp/trig.srp ---- - + + + # trig.srp -- sound event detection # # Roger B. Dannenberg @@ -961,10 +837,9 @@ class Trig(Ugen): def trig(input, reply_addr, window, threshold, pause): Trig(input, reply_addr, window, threshold, pause) # trig as a function - - -# ---- included from /Users/rbd/arco/serpent/srp/route.srp ---- - + + + # route.srp -- route input channels to output channels # # Roger B. Dannenberg @@ -999,10 +874,9 @@ class Route (Ugen): def route(chans): return Route(chans) - - -# ---- included from /Users/rbd/arco/serpent/srp/chorddetect.srp ---- - + + + def chorddetect(reply_addr): return Chorddetect(reply_addr) @@ -1023,10 +897,9 @@ class Chorddetect (Ugen): o2_send_cmd("/arco/chorddetect/repl_input", 0, "UU", id, value.id) this - - -# ---- included from /Users/rbd/arco/serpent/srp/onset.srp ---- - + + + # trig.srp -- sound event detection # # Roger B. Dannenberg @@ -1040,10 +913,9 @@ class Onset(Ugen): def onset(input, reply_addr): Onset(input, reply_addr) # onset as a function - - -# ---- included from /Users/rbd/arco/serpent/srp/spectralcentroid.srp ---- - + + + def spectralcentroid(reply_addr): return SpectralCentroid(reply_addr) @@ -1065,10 +937,9 @@ class SpectralCentroid (Ugen): this - - -# ---- included from /Users/rbd/arco/serpent/srp/spectralrolloff.srp ---- - + + + def spectralrolloff(reply_addr, threshold): return SpectralRolloff(reply_addr, threshold) @@ -1091,10 +962,9 @@ class SpectralRolloff (Ugen): - - -# ---- included from /Users/rbd/arco/serpent/srp/o2audioio.srp ---- - + + + # o2audioio.srp -- overlap add pitch shift unit generator # # Roger B. Dannenberg @@ -1121,205 +991,81 @@ def o2audioio(input, destaddr, destchans, recvchans, buffsize, sampletype, msgsize) O2audioio(input, destaddr, destchans, recvchans, buffsize, sampletype, msgsize) - - -# ---- included from /Users/rbd/arco/ugens/sttest/sttest.srp ---- - -# sttest.srp - constructor implementation + + + +# zitarev.srp - constructor implementation # # (machine generated by u2f.py) -def sttest(input, hz1, hz2): - if input.rate != 'a': - print "ERROR: 'input' input to Ugen 'sttest' must be audio rate" +def zitarev(snd, wetdry, gain, t60m): + if snd.rate != 'a': + print "ERROR: 'snd' input to Ugen 'zitarev' must be audio rate" return nil - if input.chans != 1 and input.chans != 2: - print "ERROR: 'input' input to Ugen 'sttest' must have 1 or 2 channels" + if not isnumber(wetdry) and wetdry.rate == 'a': + print "ERROR: 'wetdry' input to Ugen 'zitarev' must be block rate" return nil - if not isnumber(hz1) and hz1.chans != 1: - print "ERROR: 'hz1' input to Ugen '{c}' must be single channel" + if not isnumber(gain) and gain.rate == 'a': + print "ERROR: 'gain' input to Ugen 'zitarev' must be block rate" return nil - if not isnumber(hz2) and hz2.chans != 1: - print "ERROR: 'hz2' input to Ugen '{c}' must be single channel" + if not isnumber(t60m) and t60m.rate == 'a': + print "ERROR: 't60m' input to Ugen 'zitarev' must be block rate" return nil - - Ugen(create_ugen_id(), "sttest", 2, 'a', "Uff", omit_chans = true, 'input', - input, 'hz1', hz1, 'hz2', hz2) - - - -# ---- included from /Users/rbd/arco/serpent/srp/tableosc.srp ---- - -# tableosc.srp -- table-lookup oscillator with frequency control -# -# Roger B. Dannenberg -# Oct 2024 - -class Wavetables (Ugen): - var address_prefix // e.g. "/arco/tableosc/" - - def init(freq, amp, phase, chans, classname, rate): - if not chans: - chans = max_chans(max_chans(1, freq), amp) - super.init(create_ugen_id(), classname, chans, rate, - "UUf", 'freq', freq, 'amp', amp, 'phase', phase) - address_prefix = "/arco/" + tolower(classname) + "/" - - - def create_table(index, tlen, data, method_name): - o2_send_start() - o2_add_int32(arco_ugen_id(id)) - o2_add_int32(index) - if tlen: // omit length in case of time-domain data - o2_add_int32(tlen) - o2_add_vector(data, "f") - o2_send_finish(0, address_prefix + method_name, true) - - def create_tas(index, tlen, ampspec): - # table from amplitude spectrum - create_table(index, tlen, ampspec, "createtas") - - def create_tcs(index, tlen, spec): - # table from complex spectrum (amplitude and phase pairs) - create_table(index, tlen, spec, "createtcs") - - def create_ttd(index, samps): - # table from time-domain data (table length is len(samps)) - create_table(index, nil, samps, "createttd") - - def borrow(lender): - o2_send_start() - o2_add_int32(arco_ugen_id(id)) - o2_add_int32(arco_ugen_id(lender.id)) - o2_send_finish(0, address_prefix + "borrow", true) - - def select(index): - o2_send_start() - o2_add_int32(arco_ugen_id(id)) - o2_add_int32(index) - o2_send_finish(0, address_prefix + "sel", true) - - -class Tableosc (Wavetables): - def init(freq, amp, phase, chans): - super.init(freq, amp, phase, chans, "Tableosc", A_RATE) - - -class Tableoscb (Wavetables): - def init(freq, amp, phase, chans): - super.init(freq, amp, phase, chans, "Tableoscb", B_RATE) - - -def tableosc(freq, amp, optional chans, keyword phase = 0): - Tableosc(freq, amp, phase, chans) - - -def tableoscb(freq, amp, optional chans, keyword phase = 0): - if not isnumber(freq) and freq.rate != 'b': - print "ERROR: 'freq' input to Ugen 'tableoscb' must be block rate" + if snd.chans != 1 and snd.chans != 2: + print "ERROR: 'snd' input to Ugen 'zitarev' must have 1 or 2 channels" return nil - if not isnumber(amp) and amp.rate != 'b': - print "ERROR: 'amp' input to Ugen 'tableoscb' must be block rate" + if not isnumber(wetdry) and wetdry.chans != 1: + print "ERROR: 'wetdry' input to Ugen '{c}' must be single channel" + return nil + if not isnumber(gain) and gain.chans != 1: + print "ERROR: 'gain' input to Ugen '{c}' must be single channel" + return nil + if not isnumber(t60m) and t60m.chans != 1: + print "ERROR: 't60m' input to Ugen '{c}' must be single channel" return nil - if not chans: - chans = max_chans(max_chans(1, freq), amp) - Tableoscb(freq, amp, phase, chans) - - -# ---- included from /Users/rbd/arco/serpent/srp/blend.srp ---- - -# blend.srp -- granular synthesis from input stream unit generator -# -# Roger B. Dannenberg -# June 2023 - -BLEND_LINEAR = 0 -BLEND_POWER = 1 -BLEND_45 = 2 - -class Blend (Ugen): - def init(chans, x1, x2, b, mode, init_b): - super.init(create_ugen_id(), "Blend", chans, 'a', "UUUfif", - 'x1', x1, 'x2', x2, 'b', b, - 'mode', mode, 'init_b', init_b) - - def set_gain(gain) - o2_send_cmd("/arco/blend/gain", 0, "Uf", id, gain) - this - - def set_mode(mode) - o2_send_cmd("/arco/blend/mode", 0, "Ui", id, mode) - this - - -def blend(x1, x2, b, optional chans, mode, keyword gain = 1, init_b = 0.5): - chans = chans or max(x1.chans, max(x2.chans, b.chans)) - mode = mode or BLEND_POWER - var ugen = Blend(chans, x1, x2, b, mode, init_b) # blend as a function - if gain != 1: // default for Arco - ugen.set_gain(gain) - return ugen - - -class Blendb (Ugen): - def init(chans, x1, x2, b, mode): - super.init(create_ugen_id(), "Blendb", chans, 'b', "UUUfif", - 'x1', x1, 'x2', x2, 'b', b, 'mode', mode) - - def set_gain(gain) - o2_send_cmd("/arco/blendb/gain", 0, "Uf", id, gain) - this - - def set_mode(mode) - o2_send_cmd("/arco/blendb/mode", 0, "Ui", id, mode) - this - - -def blendb(x1, x2, b, optional chans, mode, keyword gain = 1): - chans = chans or max(x1.chans, max(x2.chans, b.chans)) - mode = mode or BLEND_POWER - var ugen = Blendb(chans, x1, x2, b, mode) # blend as a function - if gain != 1: // default for Arco - ugen.set_gain(gain) - return ugen - - -# ---- included from /Users/rbd/arco/serpent/srp/stdistr.srp ---- + Ugen(create_ugen_id(), "zitarev", 2, 'a', "UUUU", omit_chans = true, 'snd', + snd, 'wetdry', wetdry, 'gain', gain, 't60m', t60m) -# stdistr.srp -- stereo distribution + + + +# overdrive.srp - constructor implementation # -# Roger B. Dannenberg -# June 2023 - -class Stdistr (Ugen): - def init(n, width): - super.init(create_ugen_id(), "Stdistr", 2, 'a', "if", - 'n', n, 'width', width, omit_chans = true) - - def set_gain(gain) - o2_send_cmd("/arco/stdistr/gain", 0, "Uf", id, gain) - this - - def set_width(width) - o2_send_cmd("/arco/stdistr/width", 0, "Uf", id, width) - this - - def ins(index, ugen): - o2_send_cmd("/arco/stdistr/ins", 0, "UiU", id, index, ugen) - this - - def rem(index): - o2_send_cmd("/arco/stdistr/rem", 0, "Ui", id, index) - this - - -def stdistr(n, width): - Stdistr(n, width) # stdistr as a function +# (machine generated by u2f.py) +def overdrive(snd, gain, tone, volume): + if snd.rate != 'a': + print "ERROR: 'snd' input to Ugen 'overdrive' must be audio rate" + return nil + if not isnumber(gain) and gain.rate == 'a': + print "ERROR: 'gain' input to Ugen 'overdrive' must be block rate" + return nil + if not isnumber(tone) and tone.rate == 'a': + print "ERROR: 'tone' input to Ugen 'overdrive' must be block rate" + return nil + if not isnumber(volume) and volume.rate == 'a': + print "ERROR: 'volume' input to Ugen 'overdrive' must be block rate" + return nil + if snd.chans != 1 and snd.chans != 2: + print "ERROR: 'snd' input to Ugen 'overdrive' must have 1 or 2 channels" + return nil + if not isnumber(gain) and gain.chans != 1: + print "ERROR: 'gain' input to Ugen '{c}' must be single channel" + return nil + if not isnumber(tone) and tone.chans != 1: + print "ERROR: 'tone' input to Ugen '{c}' must be single channel" + return nil + if not isnumber(volume) and volume.chans != 1: + print "ERROR: 'volume' input to Ugen '{c}' must be single channel" + return nil -# ---- included from /Users/rbd/arco/serpent/srp/thru.srp ---- + Ugen(create_ugen_id(), "overdrive", 2, 'a', "UUUU", omit_chans = true, + 'snd', snd, 'gain', gain, 'tone', tone, 'volume', volume) + + + # thru.srp -- audio pass-through # # Roger B. Dannenberg @@ -1344,10 +1090,9 @@ def fanout(input, chans) # which requires the number of channels you are expanding to. You should # only use this if input is mono. Thru(input, chans) - - -# ---- included from /Users/rbd/arco/serpent/srp/zero.srp ---- - + + + # zero.srp -- audio zero # # Roger B. Dannenberg @@ -1372,10 +1117,9 @@ class Zerob (Ugen) # DO NOT DEFINE zerob() -- SEE arco.srp, WHICH DEFINES zerob() TO RETURN zerob_ugen # def zerob(): purposefully not defined here! - - -# ---- included from /Users/rbd/arco/serpent/srp/fader.srp ---- - + + + # fader.srp - constructor implementation # # Roger B. Dannenberg @@ -1450,10 +1194,9 @@ class Fader (Ugen): output_ugen.swap(this, ugen) // remove fader from ugen fade_in_lookup.remove(ugen) // otherwise, fade_in was cancelled by a fade() - - -# ---- included from /Users/rbd/arco/serpent/srp/sum.srp ---- - + + + # sum.srp -- sum and sumb unit generators # # Roger B. Dannenberg @@ -1541,4 +1284,4 @@ class Sumb(Ugen): def sumb(optional chans = 1, keyword wrap = true): Sumb(chans, wrap) - + diff --git a/apps/test/dspmanifest.txt b/apps/test/dspmanifest.txt index 87c64aa..dcb0743 100644 --- a/apps/test/dspmanifest.txt +++ b/apps/test/dspmanifest.txt @@ -32,3 +32,4 @@ sttest tableosc* blend* stdistr +overdrive \ No newline at end of file diff --git a/apps/test/init.srp b/apps/test/init.srp index 88a7142..c5a9557 100644 --- a/apps/test/init.srp +++ b/apps/test/init.srp @@ -294,6 +294,9 @@ def arco_ready(): Checkbox(win, "supersaw", 'S', 'D', WIDTH, 'S', 'supersawcheckbox') supersawcheckbox.add_target_method(nil, 'supersawtest') + Checkbox(win, "Overdrive Test", 'S', 'D', WIDTH, 'S', 'overdrivecheckbox') + overdrivecheckbox.add_target_method(nil, 'overdrivetest') + ui_initialized = true default_window.fit_to_children() @@ -1374,7 +1377,22 @@ def supersawtest(obj, event, x, y): return sss.mute() +########## Overdrive Test ############## +ovd = nil +def overdrivetest(obj, event, x, y): + display "overdrivetest", x + if x: + var noise = sine(sample_hold(ugen_rand(220.0, 880.0), + sineb(8, 1)), + 0.3) + ovd = overdrive(noise, 0.85, 0.85, 0.8) // snd, gain, tone, volume + ovd.play() + else: + ovd.set('snd', zero_ugen) + sched_select(rtsched) + sched_cause(6, ovd, 'mute') + ovd = nil ########## Main Initialization ################ diff --git a/ugens/overdrive/README.md b/ugens/overdrive/README.md new file mode 100644 index 0000000..9f32b8b --- /dev/null +++ b/ugens/overdrive/README.md @@ -0,0 +1,26 @@ +# Overdrive Unit Generator + +## Overview + +**overdrive.ugen** is an overdrive unit generator designed for **Arco**, implemented using **FAUST**. It processes stereo input, applying a high-pass filter, a nonlinear overdrive effect, a tone-adjustable low-pass filter, and volume control to shape the final output. + +## Features + +- **High-pass filtering:** Removes unwanted low frequencies below 720 Hz. +- **Nonlinear overdrive:** Applies cubic nonlinearity distortion. +- **Tone control:** Adjusts the post-filter low-pass frequency. +- **Volume control:** Scales the output level based on user-defined gain. + +## Parameters + +1. **snd** **(Stereo input signal):** The audio signal to be processed. +2. **gain** **(0.0 to 1.0):** Controls the intensity of distortion applied to the signal. +3. **tone** **(0.0 to 1.0):** Adjusts the post-filter low-pass cutoff frequency from **350 Hz** (0.0) to **4500 Hz** (1.0). +4. **volume** **(0.0 to 1.0):** Controls the output gain, scaling from **-40 dB** (0.0) to **+40 dB** (1.0). + +## Signal Processing Chain + +1. **High-pass Filtering:** A **1st-order high-pass filter** at **720 Hz** removes excessive low frequencies. +2. **Overdrive Effect:** A **cubic nonlinear function** is applied, shaping the sound with harmonic distortion. +3. **Tone Adjustment:** A **1st-order low-pass filter** dynamically scales from **350 Hz to 4500 Hz** based on `tone`. +4. **Volume Control:** The final output is **scaled exponentially** between `-40 dB` and `+40 dB`. diff --git a/ugens/overdrive/overdrive.cpp b/ugens/overdrive/overdrive.cpp new file mode 100644 index 0000000..4d9c5db --- /dev/null +++ b/ugens/overdrive/overdrive.cpp @@ -0,0 +1,178 @@ +/* overdrive -- unit generator for arco + * + * generated by f2a.py + */ + +#include "arcougen.h" +#include "overdrive.h" + +const char *Overdrive_name = "Overdrive"; + +/* O2SM INTERFACE: /arco/overdrive/new int32 id, int32 snd, int32 gain, int32 tone, int32 volume; + */ +void arco_overdrive_new(O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t snd = argv[1]->i; + int32_t gain = argv[2]->i; + int32_t tone = argv[3]->i; + int32_t volume = argv[4]->i; + // end unpack message + + ANY_UGEN_FROM_ID(snd_ugen, snd, "arco_overdrive_new"); + ANY_UGEN_FROM_ID(gain_ugen, gain, "arco_overdrive_new"); + ANY_UGEN_FROM_ID(tone_ugen, tone, "arco_overdrive_new"); + ANY_UGEN_FROM_ID(volume_ugen, volume, "arco_overdrive_new"); + + new Overdrive(id, snd_ugen, gain_ugen, tone_ugen, volume_ugen); +} + + +/* O2SM INTERFACE: /arco/overdrive/repl_snd int32 id, int32 snd_id; + */ +static void arco_overdrive_repl_snd(O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t snd_id = argv[1]->i; + // end unpack message + + UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_repl_snd"); + ANY_UGEN_FROM_ID(snd, snd_id, "arco_overdrive_repl_snd"); + overdrive->repl_snd(snd); +} + + +/* O2SM INTERFACE: /arco/overdrive/set_snd int32 id, int32 chan, float val; + */ +static void arco_overdrive_set_snd (O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t chan = argv[1]->i; + float val = argv[2]->f; + // end unpack message + + UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_set_snd"); + overdrive->set_snd(chan, val); +} + + +/* O2SM INTERFACE: /arco/overdrive/repl_gain int32 id, int32 gain_id; + */ +static void arco_overdrive_repl_gain(O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t gain_id = argv[1]->i; + // end unpack message + + UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_repl_gain"); + ANY_UGEN_FROM_ID(gain, gain_id, "arco_overdrive_repl_gain"); + overdrive->repl_gain(gain); +} + + +/* O2SM INTERFACE: /arco/overdrive/set_gain int32 id, int32 chan, float val; + */ +static void arco_overdrive_set_gain (O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t chan = argv[1]->i; + float val = argv[2]->f; + // end unpack message + + UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_set_gain"); + overdrive->set_gain(chan, val); +} + + +/* O2SM INTERFACE: /arco/overdrive/repl_tone int32 id, int32 tone_id; + */ +static void arco_overdrive_repl_tone(O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t tone_id = argv[1]->i; + // end unpack message + + UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_repl_tone"); + ANY_UGEN_FROM_ID(tone, tone_id, "arco_overdrive_repl_tone"); + overdrive->repl_tone(tone); +} + + +/* O2SM INTERFACE: /arco/overdrive/set_tone int32 id, int32 chan, float val; + */ +static void arco_overdrive_set_tone (O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t chan = argv[1]->i; + float val = argv[2]->f; + // end unpack message + + UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_set_tone"); + overdrive->set_tone(chan, val); +} + + +/* O2SM INTERFACE: /arco/overdrive/repl_volume int32 id, int32 volume_id; + */ +static void arco_overdrive_repl_volume(O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t volume_id = argv[1]->i; + // end unpack message + + UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_repl_volume"); + ANY_UGEN_FROM_ID(volume, volume_id, "arco_overdrive_repl_volume"); + overdrive->repl_volume(volume); +} + + +/* O2SM INTERFACE: /arco/overdrive/set_volume int32 id, int32 chan, float val; + */ +static void arco_overdrive_set_volume (O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t chan = argv[1]->i; + float val = argv[2]->f; + // end unpack message + + UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_set_volume"); + overdrive->set_volume(chan, val); +} + + +static void overdrive_init() +{ + // O2SM INTERFACE INITIALIZATION: (machine generated) + o2sm_method_new("/arco/overdrive/new", "iiiii", arco_overdrive_new, + NULL, true, true); + o2sm_method_new("/arco/overdrive/repl_snd", "ii", + arco_overdrive_repl_snd, NULL, true, true); + o2sm_method_new("/arco/overdrive/set_snd", "iif", + arco_overdrive_set_snd, NULL, true, true); + o2sm_method_new("/arco/overdrive/repl_gain", "ii", + arco_overdrive_repl_gain, NULL, true, true); + o2sm_method_new("/arco/overdrive/set_gain", "iif", + arco_overdrive_set_gain, NULL, true, true); + o2sm_method_new("/arco/overdrive/repl_tone", "ii", + arco_overdrive_repl_tone, NULL, true, true); + o2sm_method_new("/arco/overdrive/set_tone", "iif", + arco_overdrive_set_tone, NULL, true, true); + o2sm_method_new("/arco/overdrive/repl_volume", "ii", + arco_overdrive_repl_volume, NULL, true, true); + o2sm_method_new("/arco/overdrive/set_volume", "iif", + arco_overdrive_set_volume, NULL, true, true); + // END INTERFACE INITIALIZATION + + // class initialization code from faust: +} + +Initializer overdrive_init_obj(overdrive_init); diff --git a/ugens/overdrive/overdrive.h b/ugens/overdrive/overdrive.h new file mode 100644 index 0000000..540fcd8 --- /dev/null +++ b/ugens/overdrive/overdrive.h @@ -0,0 +1,242 @@ +/* overdrive -- unit generator for arco + * + * generated by f2a.py + */ + +/*------------- BEGIN FAUST PREAMBLE -------------*/ + +/* ------------------------------------------------------------ +name: "overdrive" +Code generated with Faust 2.74.6 (https://faust.grame.fr) +Compilation options: -lang cpp -light -ct 1 -cn Overdrive -es 1 -mcd 16 -mdd 1024 -mdy 33 -single -ftz 0 +------------------------------------------------------------ */ + +#ifndef __Overdrive_H__ +#define __Overdrive_H__ + +#ifndef FAUSTFLOAT +#define FAUSTFLOAT float +#endif + +#include +#include +#include +#include + +#ifndef FAUSTCLASS +#define FAUSTCLASS Overdrive +#endif + +#ifdef __APPLE__ +#define exp10f __exp10f +#define exp10 __exp10 +#endif + +#if defined(_WIN32) +#define RESTRICT __restrict +#else +#define RESTRICT __restrict__ +#endif + +static float Overdrive_faustpower2_f(float value) { + return value * value; +} +/*-------------- END FAUST PREAMBLE --------------*/ + +extern const char *Overdrive_name; + +class Overdrive : public Ugen { +public: + struct Overdrive_state { + FAUSTFLOAT fEntry0; + float fVec0[2]; + float fRec1[2]; + FAUSTFLOAT fEntry1; + float fVec1[2]; + float fRec0[2]; + FAUSTFLOAT fEntry2; + }; + Vec states; + void (Overdrive::*run_channel)(Overdrive_state *state); + + Ugen_ptr snd; + int snd_stride; + Sample_ptr snd_samps; + + Ugen_ptr gain; + Sample_ptr gain_samps; + + Ugen_ptr tone; + Sample_ptr tone_samps; + + Ugen_ptr volume; + Sample_ptr volume_samps; + + float fConst0; + float fConst1; + float fConst2; + float fConst3; + float fConst4; + + Overdrive(int id, Ugen_ptr snd_, Ugen_ptr gain_, Ugen_ptr tone_, Ugen_ptr volume_) : + Ugen(id, 'a', 2) { + snd = snd_; + gain = gain_; + tone = tone_; + volume = volume_; + flags = CAN_TERMINATE; + states.set_size(chans); + fConst0 = std::min(1.92e+05f, std::max(1.0f, float(AR))); + fConst1 = 3.1415927f / fConst0; + fConst2 = 1.0f / std::tan(2261.9468f / fConst0); + fConst3 = 1.0f - fConst2; + fConst4 = 1.0f / (fConst2 + 1.0f); + init_snd(snd); + init_gain(gain); + init_tone(tone); + init_volume(volume); + run_channel = (void (Overdrive::*)(Overdrive_state *)) 0; + update_run_channel(); + } + + ~Overdrive() { + snd->unref(); + gain->unref(); + tone->unref(); + volume->unref(); + } + + const char *classname() { return Overdrive_name; } + + void initialize_channel_states() { + for (int i = 0; i < chans; i++) { + for (int l0 = 0; l0 < 2; l0 = l0 + 1) { + states[i].fVec0[l0] = 0.0f; + } + for (int l1 = 0; l1 < 2; l1 = l1 + 1) { + states[i].fRec1[l1] = 0.0f; + } + for (int l2 = 0; l2 < 2; l2 = l2 + 1) { + states[i].fVec1[l2] = 0.0f; + } + for (int l3 = 0; l3 < 2; l3 = l3 + 1) { + states[i].fRec0[l3] = 0.0f; + } + } + } + + void update_run_channel() { + // initialize run_channel based on input types + void (Overdrive::*new_run_channel)(Overdrive_state *state); + if (snd->rate == 'b') { + snd = new Upsample(-1, snd->chans, snd); + } + if (gain->rate == 'a') { + gain = new Dnsampleb(-1, gain->chans, gain, LOWPASS500); + } + if (tone->rate == 'a') { + tone = new Dnsampleb(-1, tone->chans, tone, LOWPASS500); + } + if (volume->rate == 'a') { + volume = new Dnsampleb(-1, volume->chans, volume, LOWPASS500); + } + new_run_channel = &Overdrive::chan_abbb_a; + if (new_run_channel != run_channel) { + initialize_channel_states(); + run_channel = new_run_channel; + } + } + + void print_sources(int indent, bool print_flag) { + snd->print_tree(indent, print_flag, "snd"); + gain->print_tree(indent, print_flag, "gain"); + tone->print_tree(indent, print_flag, "tone"); + volume->print_tree(indent, print_flag, "volume"); + } + + void repl_snd(Ugen_ptr ugen) { + snd->unref(); + init_snd(ugen); + update_run_channel(); + } + + void repl_gain(Ugen_ptr ugen) { + gain->unref(); + init_gain(ugen); + update_run_channel(); + } + + void repl_tone(Ugen_ptr ugen) { + tone->unref(); + init_tone(ugen); + update_run_channel(); + } + + void repl_volume(Ugen_ptr ugen) { + volume->unref(); + init_volume(ugen); + update_run_channel(); + } + + void set_snd(int chan, float f) { + snd->const_set(chan, f, "Overdrive::set_snd"); + } + + void set_gain(int chan, float f) { + gain->const_set(chan, f, "Overdrive::set_gain"); + } + + void set_tone(int chan, float f) { + tone->const_set(chan, f, "Overdrive::set_tone"); + } + + void set_volume(int chan, float f) { + volume->const_set(chan, f, "Overdrive::set_volume"); + } + + void init_snd(Ugen_ptr ugen) { init_param(ugen, snd, &snd_stride); } + + void init_gain(Ugen_ptr ugen) { init_param(ugen, gain, NULL); } + + void init_tone(Ugen_ptr ugen) { init_param(ugen, tone, NULL); } + + void init_volume(Ugen_ptr ugen) { init_param(ugen, volume, NULL); } + + void chan_abbb_a(Overdrive_state *state) { + FAUSTFLOAT* input0 = snd_samps; + FAUSTFLOAT* input1 = snd_samps + snd_stride; + FAUSTFLOAT* output0 = out_samps; + FAUSTFLOAT* output1 = out_samps + BL; + float fSlow0 = 1.0f / std::tan(fConst1 * (4.15e+03f * float(tone_samps[0]) + 3.5e+02f)); + float fSlow1 = 1.0f - fSlow0; + float fSlow2 = std::pow(1e+01f, 2.0f * float(gain_samps[0])); + float fSlow3 = 1.0f / (fSlow0 + 1.0f); + float fSlow4 = std::pow(1e+01f, 2.0f * (float(volume_samps[0]) + -1.0f)); + for (int i0 = 0; i0 < BL; i0 = i0 + 1) { + float fTemp0 = float(input0[i0]) + float(input1[i0]); + state->fVec0[0] = fTemp0; + state->fRec1[0] = -(fConst4 * (fConst3 * state->fRec1[1] + fConst2 * (state->fVec0[1] - fTemp0))); + float fTemp1 = std::max(-1.0f, std::min(1.0f, fSlow2 * state->fRec1[0])); + float fTemp2 = fTemp1 * (1.0f - 0.33333334f * Overdrive_faustpower2_f(fTemp1)); + state->fVec1[0] = fTemp2; + state->fRec0[0] = fSlow3 * (fTemp2 + state->fVec1[1] - fSlow1 * state->fRec0[1]); + float fTemp3 = fSlow4 * state->fRec0[0]; + output0[i0] = FAUSTFLOAT(fTemp3); + output1[i0] = FAUSTFLOAT(fTemp3); + state->fVec0[1] = state->fVec0[0]; + state->fRec1[1] = state->fRec1[0]; + state->fVec1[1] = state->fVec1[0]; + state->fRec0[1] = state->fRec0[0]; + } + } + + void real_run() { + snd_samps = snd->run(current_block); // update input + gain_samps = gain->run(current_block); // update input + tone_samps = tone->run(current_block); // update input + volume_samps = volume->run(current_block); // update input + Overdrive_state *state = &states[0]; + (this->*run_channel)(state); + } +}; +#endif diff --git a/ugens/overdrive/overdrive.srp b/ugens/overdrive/overdrive.srp new file mode 100644 index 0000000..985768c --- /dev/null +++ b/ugens/overdrive/overdrive.srp @@ -0,0 +1,33 @@ +# overdrive.srp - constructor implementation +# +# (machine generated by u2f.py) + +def overdrive(snd, gain, tone, volume): + if snd.rate != 'a': + print "ERROR: 'snd' input to Ugen 'overdrive' must be audio rate" + return nil + if not isnumber(gain) and gain.rate == 'a': + print "ERROR: 'gain' input to Ugen 'overdrive' must be block rate" + return nil + if not isnumber(tone) and tone.rate == 'a': + print "ERROR: 'tone' input to Ugen 'overdrive' must be block rate" + return nil + if not isnumber(volume) and volume.rate == 'a': + print "ERROR: 'volume' input to Ugen 'overdrive' must be block rate" + return nil + if snd.chans != 1 and snd.chans != 2: + print "ERROR: 'snd' input to Ugen 'overdrive' must have 1 or 2 channels" + return nil + if not isnumber(gain) and gain.chans != 1: + print "ERROR: 'gain' input to Ugen '{c}' must be single channel" + return nil + if not isnumber(tone) and tone.chans != 1: + print "ERROR: 'tone' input to Ugen '{c}' must be single channel" + return nil + if not isnumber(volume) and volume.chans != 1: + print "ERROR: 'volume' input to Ugen '{c}' must be single channel" + return nil + + Ugen(create_ugen_id(), "overdrive", 2, 'a', "UUUU", omit_chans = true, + 'snd', snd, 'gain', gain, 'tone', tone, 'volume', volume) + diff --git a/ugens/overdrive/overdrive.ugen b/ugens/overdrive/overdrive.ugen new file mode 100644 index 0000000..f5618cf --- /dev/null +++ b/ugens/overdrive/overdrive.ugen @@ -0,0 +1,23 @@ +# overdrive.ugen -- Stereo Overdrive Unit Generator +# +# Vivek Mohan +# November 2024 + +overdrive(snd: 2a, gain: b, tone: b, volume: b): 2a + +FAUST + +declare name "overdrive"; +declare description "Stereo Overdrive Unit Generator for Arco"; + +import("stdfaust.lib"); + +overdrive = _,_:>_:input_filter : clipping : post_filter * volume_control<:_,_ +with{ + input_filter = fi.highpass(1, 720); + clipping = ef.cubicnl(gain, 0); + post_filter = fi.lowpass(1, 350 + (tone * (4500 - 350))); + volume_control = ba.db2linear((volume - 1) * 40); +}; + +process(left, right, gain, tone, volume) = left,right:overdrive; From 245678bc75184a7ba502bca4ef48be61f66898db Mon Sep 17 00:00:00 2001 From: vvk_mhn Date: Wed, 12 Mar 2025 22:59:01 +0530 Subject: [PATCH 2/3] Added serpent wrapper for monodistortion (renamed from overdrive) unit generator. Included documentation for the same in ugens.md --- apps/test/dspmanifest.txt | 4 +- apps/test/init.srp | 2 +- doc/ugens.md | 53 +++++- serpent/srp/overdrive.srp | 19 ++ ugens/monodistortion/monodistortion.cpp | 179 ++++++++++++++++++ .../monodistortion.h} | 134 ++++++------- ugens/monodistortion/monodistortion.srp | 22 +++ ugens/monodistortion/monodistortion.ugen | 33 ++++ ugens/overdrive/README.md | 26 --- ugens/overdrive/overdrive.cpp | 178 ----------------- ugens/overdrive/overdrive.srp | 33 ---- ugens/overdrive/overdrive.ugen | 23 --- 12 files changed, 377 insertions(+), 329 deletions(-) create mode 100644 serpent/srp/overdrive.srp create mode 100644 ugens/monodistortion/monodistortion.cpp rename ugens/{overdrive/overdrive.h => monodistortion/monodistortion.h} (55%) create mode 100644 ugens/monodistortion/monodistortion.srp create mode 100644 ugens/monodistortion/monodistortion.ugen delete mode 100644 ugens/overdrive/README.md delete mode 100644 ugens/overdrive/overdrive.cpp delete mode 100644 ugens/overdrive/overdrive.srp delete mode 100644 ugens/overdrive/overdrive.ugen diff --git a/apps/test/dspmanifest.txt b/apps/test/dspmanifest.txt index dcb0743..14fe9a0 100644 --- a/apps/test/dspmanifest.txt +++ b/apps/test/dspmanifest.txt @@ -28,8 +28,8 @@ spectralcentroid spectralrolloff o2audioio sttest -# zitarev +zitarev tableosc* blend* stdistr -overdrive \ No newline at end of file +monodistortion \ No newline at end of file diff --git a/apps/test/init.srp b/apps/test/init.srp index c5a9557..ee709b7 100644 --- a/apps/test/init.srp +++ b/apps/test/init.srp @@ -1386,7 +1386,7 @@ def overdrivetest(obj, event, x, y): var noise = sine(sample_hold(ugen_rand(220.0, 880.0), sineb(8, 1)), 0.3) - ovd = overdrive(noise, 0.85, 0.85, 0.8) // snd, gain, tone, volume + ovd = monodistortion(noise, 0.85, 0.85, 0.8) // snd, gain, tone, volume ovd.play() else: ovd.set('snd', zero_ugen) diff --git a/doc/ugens.md b/doc/ugens.md index e12e065..4ff32ae 100644 --- a/doc/ugens.md +++ b/doc/ugens.md @@ -878,7 +878,7 @@ but attenuates feedback to be almost inaudible. math, mathb(op, x1, x2, [x2_init = 0] [, chans]) mult, multb(x1, x2 [, chans]) add, addb(x1, x2 [, chans]) -sub[, subb(x1, x2 [, chans]) +sub, subb(x1, x2 [, chans]) ugen_div, ugen_divb(x1, x2 [, chans]) ugen_max, ugen_maxb(x1, x2 [, chans]) ugen_min, ugen_minb(x1, x2 [, chans]) @@ -1942,3 +1942,54 @@ zero() `/arco/zero/new id` - Create a signal consisting of all-zeros. +### monodistortion +''' +monodistortion(input, gain, tone, volume) +''' + +The `monodistortion` unit generator applies a nonlinear distortion effect +to an audio signal, modifying its harmonic content and amplitude +characteristics. The input signal can have multiple channels, which are +first summed into a mono signal using the `route` unit generator. Each +input channel is explicitly routed to a single output channel (0). The +`gain` parameter is scaled by `1/sqrt(N)`, where `N` is the number of +input channels. + +The processing chain contains a first-order high-pass filter set at 720 Hz, +which removes low-frequency components that could cause unwanted distortion +artifacts. The filtered signal is then passed through a cubic nonlinear +distortion function, controlled by the `gain` parameter, which shapes the +signal by introducing harmonic content and increasing saturation. After +distortion, the signal is passed through a low-pass filter with a cutoff +frequency determined by the `tone` parameter. This filter helps shape the +final tonal characteristics of the distorted signal, allowing for control +over the brightness or warmth of the effect. Finally, the signal is +multiplied by the `volume` parameter, controlling the output gain. + +After the signal is processed, the `wetdry` parameter is used to blend the +distorted signal with the original input. When `wetdry` is set to 0, the +output consists entirely of the original input signal. When set to 1, the +output consists only of the distorted signal. Values in between allow for a +balance between the dry and processed signals. + +`/arco/monodistortion/new id chans gain tone volume` - Create a new +monodistortion unit generator with `gain`, `tone`, and `volume` control +inputs, along with an audio input and output. + +`/arco/monodistortion/repl_gain id gain_id` - Set gain to object with id +`gain_id`. + +`/arco/monodistortion/set_gain id chan gain` - Set gain of channel `chan` to +float value `gain`. + +`/arco/monodistortion/repl_tone id tone_id` - Set tone to object with id +`tone_id`. + +`/arco/monodistortion/set_tone id chan tone` - Set tone of channel `chan` to +float value `tone`. + +`/arco/monodistortion/repl_volume id volume_id` - Set volume to object with +id `volume_id`. + +`/arco/monodistortion/set_volume id chan volume` - Set volume of channel +`chan` to float value `volume`. diff --git a/serpent/srp/overdrive.srp b/serpent/srp/overdrive.srp new file mode 100644 index 0000000..90fd798 --- /dev/null +++ b/serpent/srp/overdrive.srp @@ -0,0 +1,19 @@ +# overdrive.srp -- overdrive unit generator +# +# Vivek Mohan +# February 2025 + +class Overdrive(Instrument): + def init(input, gain, tone, volume, wetdry): + + mono_input = route(1) + for i = 0 to input.chans: + mono_input.ins(input, i, 0) + + scale_factor = 1.0 / sqrt(input.chans) + + processed = monodistortion(mono_input, gain * scale_factor, tone, volume) + + final_output = add(mult(input, subb(1, wetdry)), mult(processed, wetdry)) + + super.init("Overdrive", final_output) \ No newline at end of file diff --git a/ugens/monodistortion/monodistortion.cpp b/ugens/monodistortion/monodistortion.cpp new file mode 100644 index 0000000..a24737b --- /dev/null +++ b/ugens/monodistortion/monodistortion.cpp @@ -0,0 +1,179 @@ +/* monodistortion -- unit generator for arco + * + * generated by f2a.py + */ + +#include "arcougen.h" +#include "monodistortion.h" + +const char *Monodistortion_name = "Monodistortion"; + +/* O2SM INTERFACE: /arco/monodistortion/new int32 id, int32 chans, int32 snd, int32 gain, int32 tone, int32 volume; + */ +void arco_monodistortion_new(O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t chans = argv[1]->i; + int32_t snd = argv[2]->i; + int32_t gain = argv[3]->i; + int32_t tone = argv[4]->i; + int32_t volume = argv[5]->i; + // end unpack message + + ANY_UGEN_FROM_ID(snd_ugen, snd, "arco_monodistortion_new"); + ANY_UGEN_FROM_ID(gain_ugen, gain, "arco_monodistortion_new"); + ANY_UGEN_FROM_ID(tone_ugen, tone, "arco_monodistortion_new"); + ANY_UGEN_FROM_ID(volume_ugen, volume, "arco_monodistortion_new"); + + new Monodistortion(id, chans, snd_ugen, gain_ugen, tone_ugen, volume_ugen); +} + + +/* O2SM INTERFACE: /arco/monodistortion/repl_snd int32 id, int32 snd_id; + */ +static void arco_monodistortion_repl_snd(O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t snd_id = argv[1]->i; + // end unpack message + + UGEN_FROM_ID(Monodistortion, monodistortion, id, "arco_monodistortion_repl_snd"); + ANY_UGEN_FROM_ID(snd, snd_id, "arco_monodistortion_repl_snd"); + monodistortion->repl_snd(snd); +} + + +/* O2SM INTERFACE: /arco/monodistortion/set_snd int32 id, int32 chan, float val; + */ +static void arco_monodistortion_set_snd (O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t chan = argv[1]->i; + float val = argv[2]->f; + // end unpack message + + UGEN_FROM_ID(Monodistortion, monodistortion, id, "arco_monodistortion_set_snd"); + monodistortion->set_snd(chan, val); +} + + +/* O2SM INTERFACE: /arco/monodistortion/repl_gain int32 id, int32 gain_id; + */ +static void arco_monodistortion_repl_gain(O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t gain_id = argv[1]->i; + // end unpack message + + UGEN_FROM_ID(Monodistortion, monodistortion, id, "arco_monodistortion_repl_gain"); + ANY_UGEN_FROM_ID(gain, gain_id, "arco_monodistortion_repl_gain"); + monodistortion->repl_gain(gain); +} + + +/* O2SM INTERFACE: /arco/monodistortion/set_gain int32 id, int32 chan, float val; + */ +static void arco_monodistortion_set_gain (O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t chan = argv[1]->i; + float val = argv[2]->f; + // end unpack message + + UGEN_FROM_ID(Monodistortion, monodistortion, id, "arco_monodistortion_set_gain"); + monodistortion->set_gain(chan, val); +} + + +/* O2SM INTERFACE: /arco/monodistortion/repl_tone int32 id, int32 tone_id; + */ +static void arco_monodistortion_repl_tone(O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t tone_id = argv[1]->i; + // end unpack message + + UGEN_FROM_ID(Monodistortion, monodistortion, id, "arco_monodistortion_repl_tone"); + ANY_UGEN_FROM_ID(tone, tone_id, "arco_monodistortion_repl_tone"); + monodistortion->repl_tone(tone); +} + + +/* O2SM INTERFACE: /arco/monodistortion/set_tone int32 id, int32 chan, float val; + */ +static void arco_monodistortion_set_tone (O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t chan = argv[1]->i; + float val = argv[2]->f; + // end unpack message + + UGEN_FROM_ID(Monodistortion, monodistortion, id, "arco_monodistortion_set_tone"); + monodistortion->set_tone(chan, val); +} + + +/* O2SM INTERFACE: /arco/monodistortion/repl_volume int32 id, int32 volume_id; + */ +static void arco_monodistortion_repl_volume(O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t volume_id = argv[1]->i; + // end unpack message + + UGEN_FROM_ID(Monodistortion, monodistortion, id, "arco_monodistortion_repl_volume"); + ANY_UGEN_FROM_ID(volume, volume_id, "arco_monodistortion_repl_volume"); + monodistortion->repl_volume(volume); +} + + +/* O2SM INTERFACE: /arco/monodistortion/set_volume int32 id, int32 chan, float val; + */ +static void arco_monodistortion_set_volume (O2SM_HANDLER_ARGS) +{ + // begin unpack message (machine-generated): + int32_t id = argv[0]->i; + int32_t chan = argv[1]->i; + float val = argv[2]->f; + // end unpack message + + UGEN_FROM_ID(Monodistortion, monodistortion, id, "arco_monodistortion_set_volume"); + monodistortion->set_volume(chan, val); +} + + +static void monodistortion_init() +{ + // O2SM INTERFACE INITIALIZATION: (machine generated) + o2sm_method_new("/arco/monodistortion/new", "iiiiii", + arco_monodistortion_new, NULL, true, true); + o2sm_method_new("/arco/monodistortion/repl_snd", "ii", + arco_monodistortion_repl_snd, NULL, true, true); + o2sm_method_new("/arco/monodistortion/set_snd", "iif", + arco_monodistortion_set_snd, NULL, true, true); + o2sm_method_new("/arco/monodistortion/repl_gain", "ii", + arco_monodistortion_repl_gain, NULL, true, true); + o2sm_method_new("/arco/monodistortion/set_gain", "iif", + arco_monodistortion_set_gain, NULL, true, true); + o2sm_method_new("/arco/monodistortion/repl_tone", "ii", + arco_monodistortion_repl_tone, NULL, true, true); + o2sm_method_new("/arco/monodistortion/set_tone", "iif", + arco_monodistortion_set_tone, NULL, true, true); + o2sm_method_new("/arco/monodistortion/repl_volume", "ii", + arco_monodistortion_repl_volume, NULL, true, true); + o2sm_method_new("/arco/monodistortion/set_volume", "iif", + arco_monodistortion_set_volume, NULL, true, true); + // END INTERFACE INITIALIZATION + + // class initialization code from faust: +} + +Initializer monodistortion_init_obj(monodistortion_init); diff --git a/ugens/overdrive/overdrive.h b/ugens/monodistortion/monodistortion.h similarity index 55% rename from ugens/overdrive/overdrive.h rename to ugens/monodistortion/monodistortion.h index 540fcd8..052864e 100644 --- a/ugens/overdrive/overdrive.h +++ b/ugens/monodistortion/monodistortion.h @@ -1,4 +1,4 @@ -/* overdrive -- unit generator for arco +/* monodistortion -- unit generator for arco * * generated by f2a.py */ @@ -6,13 +6,13 @@ /*------------- BEGIN FAUST PREAMBLE -------------*/ /* ------------------------------------------------------------ -name: "overdrive" +name: "monodistortion" Code generated with Faust 2.74.6 (https://faust.grame.fr) -Compilation options: -lang cpp -light -ct 1 -cn Overdrive -es 1 -mcd 16 -mdd 1024 -mdy 33 -single -ftz 0 +Compilation options: -lang cpp -light -ct 1 -cn Monodistortion -es 1 -mcd 16 -mdd 1024 -mdy 33 -single -ftz 0 ------------------------------------------------------------ */ -#ifndef __Overdrive_H__ -#define __Overdrive_H__ +#ifndef __Monodistortion_H__ +#define __Monodistortion_H__ #ifndef FAUSTFLOAT #define FAUSTFLOAT float @@ -24,7 +24,7 @@ Compilation options: -lang cpp -light -ct 1 -cn Overdrive -es 1 -mcd 16 -mdd 102 #include #ifndef FAUSTCLASS -#define FAUSTCLASS Overdrive +#define FAUSTCLASS Monodistortion #endif #ifdef __APPLE__ @@ -38,38 +38,41 @@ Compilation options: -lang cpp -light -ct 1 -cn Overdrive -es 1 -mcd 16 -mdd 102 #define RESTRICT __restrict__ #endif -static float Overdrive_faustpower2_f(float value) { +static float Monodistortion_faustpower2_f(float value) { return value * value; } /*-------------- END FAUST PREAMBLE --------------*/ -extern const char *Overdrive_name; +extern const char *Monodistortion_name; -class Overdrive : public Ugen { +class Monodistortion : public Ugen { public: - struct Overdrive_state { - FAUSTFLOAT fEntry0; + struct Monodistortion_state { float fVec0[2]; float fRec1[2]; - FAUSTFLOAT fEntry1; + FAUSTFLOAT fEntry0; float fVec1[2]; + FAUSTFLOAT fEntry1; float fRec0[2]; FAUSTFLOAT fEntry2; }; - Vec states; - void (Overdrive::*run_channel)(Overdrive_state *state); + Vec states; + void (Monodistortion::*run_channel)(Monodistortion_state *state); Ugen_ptr snd; int snd_stride; Sample_ptr snd_samps; Ugen_ptr gain; + int gain_stride; Sample_ptr gain_samps; Ugen_ptr tone; + int tone_stride; Sample_ptr tone_samps; Ugen_ptr volume; + int volume_stride; Sample_ptr volume_samps; float fConst0; @@ -78,8 +81,8 @@ class Overdrive : public Ugen { float fConst3; float fConst4; - Overdrive(int id, Ugen_ptr snd_, Ugen_ptr gain_, Ugen_ptr tone_, Ugen_ptr volume_) : - Ugen(id, 'a', 2) { + Monodistortion(int id, int nchans, Ugen_ptr snd_, Ugen_ptr gain_, Ugen_ptr tone_, Ugen_ptr volume_) : + Ugen(id, 'a', nchans) { snd = snd_; gain = gain_; tone = tone_; @@ -87,26 +90,26 @@ class Overdrive : public Ugen { flags = CAN_TERMINATE; states.set_size(chans); fConst0 = std::min(1.92e+05f, std::max(1.0f, float(AR))); - fConst1 = 3.1415927f / fConst0; - fConst2 = 1.0f / std::tan(2261.9468f / fConst0); - fConst3 = 1.0f - fConst2; - fConst4 = 1.0f / (fConst2 + 1.0f); + fConst1 = 1.0f / std::tan(2261.9468f / fConst0); + fConst2 = 1.0f - fConst1; + fConst3 = 1.0f / (fConst1 + 1.0f); + fConst4 = 3.1415927f / fConst0; init_snd(snd); init_gain(gain); init_tone(tone); init_volume(volume); - run_channel = (void (Overdrive::*)(Overdrive_state *)) 0; + run_channel = (void (Monodistortion::*)(Monodistortion_state *)) 0; update_run_channel(); } - ~Overdrive() { + ~Monodistortion() { snd->unref(); gain->unref(); tone->unref(); volume->unref(); } - const char *classname() { return Overdrive_name; } + const char *classname() { return Monodistortion_name; } void initialize_channel_states() { for (int i = 0; i < chans; i++) { @@ -127,24 +130,21 @@ class Overdrive : public Ugen { void update_run_channel() { // initialize run_channel based on input types - void (Overdrive::*new_run_channel)(Overdrive_state *state); - if (snd->rate == 'b') { - snd = new Upsample(-1, snd->chans, snd); - } - if (gain->rate == 'a') { - gain = new Dnsampleb(-1, gain->chans, gain, LOWPASS500); - } - if (tone->rate == 'a') { - tone = new Dnsampleb(-1, tone->chans, tone, LOWPASS500); - } - if (volume->rate == 'a') { - volume = new Dnsampleb(-1, volume->chans, volume, LOWPASS500); - } - new_run_channel = &Overdrive::chan_abbb_a; - if (new_run_channel != run_channel) { - initialize_channel_states(); - run_channel = new_run_channel; + void (Monodistortion::*new_run_channel)(Monodistortion_state *state); + if (snd->rate == 'b') { + snd = new Upsample(-1, snd->chans, snd); + } + if (gain->rate == 'a') { + gain = new Dnsampleb(-1, gain->chans, gain, LOWPASS500); + } + if (tone->rate == 'a') { + tone = new Dnsampleb(-1, tone->chans, tone, LOWPASS500); } + if (volume->rate == 'a') { + volume = new Dnsampleb(-1, volume->chans, volume, LOWPASS500); + } + new_run_channel = &Monodistortion::chan_abbb_a; + run_channel = new_run_channel; } void print_sources(int indent, bool print_flag) { @@ -179,50 +179,46 @@ class Overdrive : public Ugen { } void set_snd(int chan, float f) { - snd->const_set(chan, f, "Overdrive::set_snd"); + snd->const_set(chan, f, "Monodistortion::set_snd"); } void set_gain(int chan, float f) { - gain->const_set(chan, f, "Overdrive::set_gain"); + gain->const_set(chan, f, "Monodistortion::set_gain"); } void set_tone(int chan, float f) { - tone->const_set(chan, f, "Overdrive::set_tone"); + tone->const_set(chan, f, "Monodistortion::set_tone"); } void set_volume(int chan, float f) { - volume->const_set(chan, f, "Overdrive::set_volume"); + volume->const_set(chan, f, "Monodistortion::set_volume"); } void init_snd(Ugen_ptr ugen) { init_param(ugen, snd, &snd_stride); } - void init_gain(Ugen_ptr ugen) { init_param(ugen, gain, NULL); } + void init_gain(Ugen_ptr ugen) { init_param(ugen, gain, &gain_stride); } - void init_tone(Ugen_ptr ugen) { init_param(ugen, tone, NULL); } + void init_tone(Ugen_ptr ugen) { init_param(ugen, tone, &tone_stride); } - void init_volume(Ugen_ptr ugen) { init_param(ugen, volume, NULL); } + void init_volume(Ugen_ptr ugen) { init_param(ugen, volume, &volume_stride); } - void chan_abbb_a(Overdrive_state *state) { + void chan_abbb_a(Monodistortion_state *state) { FAUSTFLOAT* input0 = snd_samps; - FAUSTFLOAT* input1 = snd_samps + snd_stride; FAUSTFLOAT* output0 = out_samps; - FAUSTFLOAT* output1 = out_samps + BL; - float fSlow0 = 1.0f / std::tan(fConst1 * (4.15e+03f * float(tone_samps[0]) + 3.5e+02f)); - float fSlow1 = 1.0f - fSlow0; - float fSlow2 = std::pow(1e+01f, 2.0f * float(gain_samps[0])); - float fSlow3 = 1.0f / (fSlow0 + 1.0f); - float fSlow4 = std::pow(1e+01f, 2.0f * (float(volume_samps[0]) + -1.0f)); + float fSlow0 = std::pow(1e+01f, 2.0f * float(gain_samps[0])); + float fSlow1 = 1.0f / std::tan(fConst4 * float(tone_samps[0])); + float fSlow2 = 1.0f - fSlow1; + float fSlow3 = 1.0f / (fSlow1 + 1.0f); + float fSlow4 = float(volume_samps[0]); for (int i0 = 0; i0 < BL; i0 = i0 + 1) { - float fTemp0 = float(input0[i0]) + float(input1[i0]); + float fTemp0 = float(input0[i0]); state->fVec0[0] = fTemp0; - state->fRec1[0] = -(fConst4 * (fConst3 * state->fRec1[1] + fConst2 * (state->fVec0[1] - fTemp0))); - float fTemp1 = std::max(-1.0f, std::min(1.0f, fSlow2 * state->fRec1[0])); - float fTemp2 = fTemp1 * (1.0f - 0.33333334f * Overdrive_faustpower2_f(fTemp1)); + state->fRec1[0] = -(fConst3 * (fConst2 * state->fRec1[1] + fConst1 * (state->fVec0[1] - fTemp0))); + float fTemp1 = std::max(-1.0f, std::min(1.0f, fSlow0 * state->fRec1[0])); + float fTemp2 = fTemp1 * (1.0f - 0.33333334f * Monodistortion_faustpower2_f(fTemp1)); state->fVec1[0] = fTemp2; - state->fRec0[0] = fSlow3 * (fTemp2 + state->fVec1[1] - fSlow1 * state->fRec0[1]); - float fTemp3 = fSlow4 * state->fRec0[0]; - output0[i0] = FAUSTFLOAT(fTemp3); - output1[i0] = FAUSTFLOAT(fTemp3); + state->fRec0[0] = -(fSlow3 * (fSlow2 * state->fRec0[1] - (fTemp2 + state->fVec1[1]))); + output0[i0] = FAUSTFLOAT(fSlow4 * state->fRec0[0]); state->fVec0[1] = state->fVec0[0]; state->fRec1[1] = state->fRec1[0]; state->fVec1[1] = state->fVec1[0]; @@ -235,8 +231,16 @@ class Overdrive : public Ugen { gain_samps = gain->run(current_block); // update input tone_samps = tone->run(current_block); // update input volume_samps = volume->run(current_block); // update input - Overdrive_state *state = &states[0]; - (this->*run_channel)(state); + Monodistortion_state *state = &states[0]; + for (int i = 0; i < chans; i++) { + (this->*run_channel)(state); + state++; + out_samps += BL; + audio_samps += audio_stride; + gain_samps += gain_stride; + tone_samps += tone_stride; + volume_samps += volume_stride; + } } }; #endif diff --git a/ugens/monodistortion/monodistortion.srp b/ugens/monodistortion/monodistortion.srp new file mode 100644 index 0000000..fa2c345 --- /dev/null +++ b/ugens/monodistortion/monodistortion.srp @@ -0,0 +1,22 @@ +# monodistortion.srp - constructor implementation +# +# (machine generated by u2f.py) + +def monodistortion(snd, gain, tone, volume, optional chans): + if snd.rate != 'a': + print "ERROR: 'snd' input to Ugen 'monodistortion' must be audio rate" + return nil + if not isnumber(gain) and gain.rate == 'a': + print "ERROR: 'gain' input to Ugen 'monodistortion' must be block rate" + return nil + if not isnumber(tone) and tone.rate == 'a': + print "ERROR: 'tone' input to Ugen 'monodistortion' must be block rate" + return nil + if not isnumber(volume) and volume.rate == 'a': + print "ERROR: 'volume' input to Ugen 'monodistortion' must be block rate" + return nil + if not chans: + chans = max_chans(max_chans(max_chans(max_chans(1, snd), gain), tone), volume) + Ugen(create_ugen_id(), "monodistortion", chans, 'a', "UUUU", 'snd', snd, + 'gain', gain, 'tone', tone, 'volume', volume) + diff --git a/ugens/monodistortion/monodistortion.ugen b/ugens/monodistortion/monodistortion.ugen new file mode 100644 index 0000000..55df6c6 --- /dev/null +++ b/ugens/monodistortion/monodistortion.ugen @@ -0,0 +1,33 @@ +# monodistortion.ugen -- Overdrive Unit Generator +# +# Vivek Mohan +# November 2024 + +#Parameters + +#snd: N-channel input signal. +#gain: Controls the intensity of distortion applied to the signal (Range: 0 to 1). +#tone: Adjusts the post-filter low-pass cutoff frequency. +#volume: Controls the output gain. + +monodistortion(snd: a, gain: b, tone: b, volume: b): a + +FAUST + +declare name "monodistortion"; +declare description "Overdrive Unit Generator for Arco"; + +import("stdfaust.lib"); + +monodistortion = _: input_filter : clipping : post_filter * volume + +with { + + input_filter = fi.highpass(1, 720); + + clipping = ef.cubicnl(gain, 0); + + post_filter = fi.lowpass(1, tone); +}; + +process(audio, gain, tone, volume) = audio:monodistortion; \ No newline at end of file diff --git a/ugens/overdrive/README.md b/ugens/overdrive/README.md deleted file mode 100644 index 9f32b8b..0000000 --- a/ugens/overdrive/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Overdrive Unit Generator - -## Overview - -**overdrive.ugen** is an overdrive unit generator designed for **Arco**, implemented using **FAUST**. It processes stereo input, applying a high-pass filter, a nonlinear overdrive effect, a tone-adjustable low-pass filter, and volume control to shape the final output. - -## Features - -- **High-pass filtering:** Removes unwanted low frequencies below 720 Hz. -- **Nonlinear overdrive:** Applies cubic nonlinearity distortion. -- **Tone control:** Adjusts the post-filter low-pass frequency. -- **Volume control:** Scales the output level based on user-defined gain. - -## Parameters - -1. **snd** **(Stereo input signal):** The audio signal to be processed. -2. **gain** **(0.0 to 1.0):** Controls the intensity of distortion applied to the signal. -3. **tone** **(0.0 to 1.0):** Adjusts the post-filter low-pass cutoff frequency from **350 Hz** (0.0) to **4500 Hz** (1.0). -4. **volume** **(0.0 to 1.0):** Controls the output gain, scaling from **-40 dB** (0.0) to **+40 dB** (1.0). - -## Signal Processing Chain - -1. **High-pass Filtering:** A **1st-order high-pass filter** at **720 Hz** removes excessive low frequencies. -2. **Overdrive Effect:** A **cubic nonlinear function** is applied, shaping the sound with harmonic distortion. -3. **Tone Adjustment:** A **1st-order low-pass filter** dynamically scales from **350 Hz to 4500 Hz** based on `tone`. -4. **Volume Control:** The final output is **scaled exponentially** between `-40 dB` and `+40 dB`. diff --git a/ugens/overdrive/overdrive.cpp b/ugens/overdrive/overdrive.cpp deleted file mode 100644 index 4d9c5db..0000000 --- a/ugens/overdrive/overdrive.cpp +++ /dev/null @@ -1,178 +0,0 @@ -/* overdrive -- unit generator for arco - * - * generated by f2a.py - */ - -#include "arcougen.h" -#include "overdrive.h" - -const char *Overdrive_name = "Overdrive"; - -/* O2SM INTERFACE: /arco/overdrive/new int32 id, int32 snd, int32 gain, int32 tone, int32 volume; - */ -void arco_overdrive_new(O2SM_HANDLER_ARGS) -{ - // begin unpack message (machine-generated): - int32_t id = argv[0]->i; - int32_t snd = argv[1]->i; - int32_t gain = argv[2]->i; - int32_t tone = argv[3]->i; - int32_t volume = argv[4]->i; - // end unpack message - - ANY_UGEN_FROM_ID(snd_ugen, snd, "arco_overdrive_new"); - ANY_UGEN_FROM_ID(gain_ugen, gain, "arco_overdrive_new"); - ANY_UGEN_FROM_ID(tone_ugen, tone, "arco_overdrive_new"); - ANY_UGEN_FROM_ID(volume_ugen, volume, "arco_overdrive_new"); - - new Overdrive(id, snd_ugen, gain_ugen, tone_ugen, volume_ugen); -} - - -/* O2SM INTERFACE: /arco/overdrive/repl_snd int32 id, int32 snd_id; - */ -static void arco_overdrive_repl_snd(O2SM_HANDLER_ARGS) -{ - // begin unpack message (machine-generated): - int32_t id = argv[0]->i; - int32_t snd_id = argv[1]->i; - // end unpack message - - UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_repl_snd"); - ANY_UGEN_FROM_ID(snd, snd_id, "arco_overdrive_repl_snd"); - overdrive->repl_snd(snd); -} - - -/* O2SM INTERFACE: /arco/overdrive/set_snd int32 id, int32 chan, float val; - */ -static void arco_overdrive_set_snd (O2SM_HANDLER_ARGS) -{ - // begin unpack message (machine-generated): - int32_t id = argv[0]->i; - int32_t chan = argv[1]->i; - float val = argv[2]->f; - // end unpack message - - UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_set_snd"); - overdrive->set_snd(chan, val); -} - - -/* O2SM INTERFACE: /arco/overdrive/repl_gain int32 id, int32 gain_id; - */ -static void arco_overdrive_repl_gain(O2SM_HANDLER_ARGS) -{ - // begin unpack message (machine-generated): - int32_t id = argv[0]->i; - int32_t gain_id = argv[1]->i; - // end unpack message - - UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_repl_gain"); - ANY_UGEN_FROM_ID(gain, gain_id, "arco_overdrive_repl_gain"); - overdrive->repl_gain(gain); -} - - -/* O2SM INTERFACE: /arco/overdrive/set_gain int32 id, int32 chan, float val; - */ -static void arco_overdrive_set_gain (O2SM_HANDLER_ARGS) -{ - // begin unpack message (machine-generated): - int32_t id = argv[0]->i; - int32_t chan = argv[1]->i; - float val = argv[2]->f; - // end unpack message - - UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_set_gain"); - overdrive->set_gain(chan, val); -} - - -/* O2SM INTERFACE: /arco/overdrive/repl_tone int32 id, int32 tone_id; - */ -static void arco_overdrive_repl_tone(O2SM_HANDLER_ARGS) -{ - // begin unpack message (machine-generated): - int32_t id = argv[0]->i; - int32_t tone_id = argv[1]->i; - // end unpack message - - UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_repl_tone"); - ANY_UGEN_FROM_ID(tone, tone_id, "arco_overdrive_repl_tone"); - overdrive->repl_tone(tone); -} - - -/* O2SM INTERFACE: /arco/overdrive/set_tone int32 id, int32 chan, float val; - */ -static void arco_overdrive_set_tone (O2SM_HANDLER_ARGS) -{ - // begin unpack message (machine-generated): - int32_t id = argv[0]->i; - int32_t chan = argv[1]->i; - float val = argv[2]->f; - // end unpack message - - UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_set_tone"); - overdrive->set_tone(chan, val); -} - - -/* O2SM INTERFACE: /arco/overdrive/repl_volume int32 id, int32 volume_id; - */ -static void arco_overdrive_repl_volume(O2SM_HANDLER_ARGS) -{ - // begin unpack message (machine-generated): - int32_t id = argv[0]->i; - int32_t volume_id = argv[1]->i; - // end unpack message - - UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_repl_volume"); - ANY_UGEN_FROM_ID(volume, volume_id, "arco_overdrive_repl_volume"); - overdrive->repl_volume(volume); -} - - -/* O2SM INTERFACE: /arco/overdrive/set_volume int32 id, int32 chan, float val; - */ -static void arco_overdrive_set_volume (O2SM_HANDLER_ARGS) -{ - // begin unpack message (machine-generated): - int32_t id = argv[0]->i; - int32_t chan = argv[1]->i; - float val = argv[2]->f; - // end unpack message - - UGEN_FROM_ID(Overdrive, overdrive, id, "arco_overdrive_set_volume"); - overdrive->set_volume(chan, val); -} - - -static void overdrive_init() -{ - // O2SM INTERFACE INITIALIZATION: (machine generated) - o2sm_method_new("/arco/overdrive/new", "iiiii", arco_overdrive_new, - NULL, true, true); - o2sm_method_new("/arco/overdrive/repl_snd", "ii", - arco_overdrive_repl_snd, NULL, true, true); - o2sm_method_new("/arco/overdrive/set_snd", "iif", - arco_overdrive_set_snd, NULL, true, true); - o2sm_method_new("/arco/overdrive/repl_gain", "ii", - arco_overdrive_repl_gain, NULL, true, true); - o2sm_method_new("/arco/overdrive/set_gain", "iif", - arco_overdrive_set_gain, NULL, true, true); - o2sm_method_new("/arco/overdrive/repl_tone", "ii", - arco_overdrive_repl_tone, NULL, true, true); - o2sm_method_new("/arco/overdrive/set_tone", "iif", - arco_overdrive_set_tone, NULL, true, true); - o2sm_method_new("/arco/overdrive/repl_volume", "ii", - arco_overdrive_repl_volume, NULL, true, true); - o2sm_method_new("/arco/overdrive/set_volume", "iif", - arco_overdrive_set_volume, NULL, true, true); - // END INTERFACE INITIALIZATION - - // class initialization code from faust: -} - -Initializer overdrive_init_obj(overdrive_init); diff --git a/ugens/overdrive/overdrive.srp b/ugens/overdrive/overdrive.srp deleted file mode 100644 index 985768c..0000000 --- a/ugens/overdrive/overdrive.srp +++ /dev/null @@ -1,33 +0,0 @@ -# overdrive.srp - constructor implementation -# -# (machine generated by u2f.py) - -def overdrive(snd, gain, tone, volume): - if snd.rate != 'a': - print "ERROR: 'snd' input to Ugen 'overdrive' must be audio rate" - return nil - if not isnumber(gain) and gain.rate == 'a': - print "ERROR: 'gain' input to Ugen 'overdrive' must be block rate" - return nil - if not isnumber(tone) and tone.rate == 'a': - print "ERROR: 'tone' input to Ugen 'overdrive' must be block rate" - return nil - if not isnumber(volume) and volume.rate == 'a': - print "ERROR: 'volume' input to Ugen 'overdrive' must be block rate" - return nil - if snd.chans != 1 and snd.chans != 2: - print "ERROR: 'snd' input to Ugen 'overdrive' must have 1 or 2 channels" - return nil - if not isnumber(gain) and gain.chans != 1: - print "ERROR: 'gain' input to Ugen '{c}' must be single channel" - return nil - if not isnumber(tone) and tone.chans != 1: - print "ERROR: 'tone' input to Ugen '{c}' must be single channel" - return nil - if not isnumber(volume) and volume.chans != 1: - print "ERROR: 'volume' input to Ugen '{c}' must be single channel" - return nil - - Ugen(create_ugen_id(), "overdrive", 2, 'a', "UUUU", omit_chans = true, - 'snd', snd, 'gain', gain, 'tone', tone, 'volume', volume) - diff --git a/ugens/overdrive/overdrive.ugen b/ugens/overdrive/overdrive.ugen deleted file mode 100644 index f5618cf..0000000 --- a/ugens/overdrive/overdrive.ugen +++ /dev/null @@ -1,23 +0,0 @@ -# overdrive.ugen -- Stereo Overdrive Unit Generator -# -# Vivek Mohan -# November 2024 - -overdrive(snd: 2a, gain: b, tone: b, volume: b): 2a - -FAUST - -declare name "overdrive"; -declare description "Stereo Overdrive Unit Generator for Arco"; - -import("stdfaust.lib"); - -overdrive = _,_:>_:input_filter : clipping : post_filter * volume_control<:_,_ -with{ - input_filter = fi.highpass(1, 720); - clipping = ef.cubicnl(gain, 0); - post_filter = fi.lowpass(1, 350 + (tone * (4500 - 350))); - volume_control = ba.db2linear((volume - 1) * 40); -}; - -process(left, right, gain, tone, volume) = left,right:overdrive; From ba89bd6f93bd74dabb8766d1be23592adfe6a6d8 Mon Sep 17 00:00:00 2001 From: vvk_mhn Date: Fri, 21 Mar 2025 16:27:24 +0530 Subject: [PATCH 3/3] Applied the scale factor to volume (post-distortion) instead of gain, changed the test code in init.srp, updated ugens.md --- apps/test/init.srp | 20 ++++++++++++++---- doc/ugens.md | 43 +++++++++++++++++---------------------- serpent/srp/overdrive.srp | 4 ++-- 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/apps/test/init.srp b/apps/test/init.srp index ee709b7..a762038 100644 --- a/apps/test/init.srp +++ b/apps/test/init.srp @@ -1380,20 +1380,32 @@ def supersawtest(obj, event, x, y): ########## Overdrive Test ############## ovd = nil + def overdrivetest(obj, event, x, y): display "overdrivetest", x if x: - var noise = sine(sample_hold(ugen_rand(220.0, 880.0), - sineb(8, 1)), - 0.3) - ovd = monodistortion(noise, 0.85, 0.85, 0.8) // snd, gain, tone, volume + + var left_tone = sine(220, 0.3) // 220 Hz (A3) + var right_tone = sine(275, 0.3) // 275 Hz (~C#4) + + var env = pweb(0.2, 0.5, 1) + var left_env = mult(left_tone, env) + var right_env = mult(right_tone, env) + + // Mix left and right signals into a single mono signal before distortion + var mono_input = add(left_env, right_env) + + // Apply distortion + ovd = monodistortion(mono_input, 0.85, 0.85, 0.8) // snd, gain, tone, volume ovd.play() else: + // Fade out and stop processing ovd.set('snd', zero_ugen) sched_select(rtsched) sched_cause(6, ovd, 'mute') ovd = nil + ########## Main Initialization ################ def arco_prugens_handler(rest ignore): diff --git a/doc/ugens.md b/doc/ugens.md index 4ff32ae..d0ae32a 100644 --- a/doc/ugens.md +++ b/doc/ugens.md @@ -878,7 +878,7 @@ but attenuates feedback to be almost inaudible. math, mathb(op, x1, x2, [x2_init = 0] [, chans]) mult, multb(x1, x2 [, chans]) add, addb(x1, x2 [, chans]) -sub, subb(x1, x2 [, chans]) +sub[, subb(x1, x2 [, chans]) ugen_div, ugen_divb(x1, x2 [, chans]) ugen_max, ugen_maxb(x1, x2 [, chans]) ugen_min, ugen_minb(x1, x2 [, chans]) @@ -1946,31 +1946,26 @@ zero() ''' monodistortion(input, gain, tone, volume) ''' - -The `monodistortion` unit generator applies a nonlinear distortion effect -to an audio signal, modifying its harmonic content and amplitude -characteristics. The input signal can have multiple channels, which are -first summed into a mono signal using the `route` unit generator. Each -input channel is explicitly routed to a single output channel (0). The -`gain` parameter is scaled by `1/sqrt(N)`, where `N` is the number of -input channels. - -The processing chain contains a first-order high-pass filter set at 720 Hz, -which removes low-frequency components that could cause unwanted distortion -artifacts. The filtered signal is then passed through a cubic nonlinear -distortion function, controlled by the `gain` parameter, which shapes the -signal by introducing harmonic content and increasing saturation. After -distortion, the signal is passed through a low-pass filter with a cutoff -frequency determined by the `tone` parameter. This filter helps shape the -final tonal characteristics of the distorted signal, allowing for control -over the brightness or warmth of the effect. Finally, the signal is +The input signal can have multiple channels, which are first summed into +a mono signal and scaled by `gain`. After processing in mono, the signal +is fanned out and blended with the original input signal using `wetdry` +to control the balance from pure input (`wetdry` = 0) to pure effect +(`wetdry` = 1). + +The processing chain contains a first-order high-pass filter set at 720 Hz, +which removes low-frequency components. The filtered signal is then passed +through a cubic nonlinear distortion function, which shapes the +signal by introducing harmonic content and saturation. After +distortion, the signal is passed through a low-pass filter with a cutoff +frequency determined by the `tone` parameter (in Hz), allowing for control +over the brightness or warmth of the effect. Finally, the signal is multiplied by the `volume` parameter, controlling the output gain. -After the signal is processed, the `wetdry` parameter is used to blend the -distorted signal with the original input. When `wetdry` is set to 0, the -output consists entirely of the original input signal. When set to 1, the -output consists only of the distorted signal. Values in between allow for a -balance between the dry and processed signals. +After the signal is processed, the `wetdry` parameter is used to blend the +distorted signal with the original input. When `wetdry` is set to 0, the +output consists entirely of the original input signal. When set to 1, the +output consists only of the distorted signal. Values in between allow for a +balance between the dry and processed signals `/arco/monodistortion/new id chans gain tone volume` - Create a new monodistortion unit generator with `gain`, `tone`, and `volume` control diff --git a/serpent/srp/overdrive.srp b/serpent/srp/overdrive.srp index 90fd798..72cc3af 100644 --- a/serpent/srp/overdrive.srp +++ b/serpent/srp/overdrive.srp @@ -12,8 +12,8 @@ class Overdrive(Instrument): scale_factor = 1.0 / sqrt(input.chans) - processed = monodistortion(mono_input, gain * scale_factor, tone, volume) + processed = monodistortion(mono_input, gain, tone, volume * scale_factor) - final_output = add(mult(input, subb(1, wetdry)), mult(processed, wetdry)) + final_output = add(mult(input, mult(scale_factor,subb(1, wetdry))), mult(processed, mult(scale_factor,wetdry))) super.init("Overdrive", final_output) \ No newline at end of file