diff --git a/Synth/tilr_JSAdditiv.jsfx b/Synth/tilr_JSAdditiv.jsfx new file mode 100644 index 0000000..c8d2570 --- /dev/null +++ b/Synth/tilr_JSAdditiv.jsfx @@ -0,0 +1,580 @@ +desc: JSAdditiv +author: tilr +version: 1.1 +provides: + tilr_JSAdditiv/add.adsr.jsfx-inc + tilr_JSAdditiv/add.array.jsfx-inc + tilr_JSAdditiv/add.fft_real_synth.jsfx-inc + tilr_JSAdditiv/add.gfxlib.jsfx-inc + tilr_JSAdditiv/add.mouselib.jsfx-inc + tilr_JSAdditiv/add.wavetable.jsfx-inc + tilr_JSAdditiv/add.zdf_filter.jsfx-inc + [data] tilr_JSAdditiv/Complex 1.wav + [data] tilr_JSAdditiv/Complex 2.wav + [data] tilr_JSAdditiv/Complex 3.wav + [data] tilr_JSAdditiv/Complex 4.wav + [data] tilr_JSAdditiv/Complex 5.wav + [data] tilr_JSAdditiv/Organ 1.wav + [data] tilr_JSAdditiv/Organ 2.wav + [data] tilr_JSAdditiv/Organ 3.wav + [data] tilr_JSAdditiv/Organ 4.wav + [data] tilr_JSAdditiv/Organ 5.wav + [data] tilr_JSAdditiv/Organ 6.wav + [data] tilr_JSAdditiv/Organ 7.wav + [data] tilr_JSAdditiv/Saw 1.wav + [data] tilr_JSAdditiv/Saw 2.wav + [data] tilr_JSAdditiv/Saw 3.wav + [data] tilr_JSAdditiv/Sine 1.wav + [data] tilr_JSAdditiv/Sine 2.wav + [data] tilr_JSAdditiv/Sine 3.wav + [data] tilr_JSAdditiv/Sine 4.wav + [data] tilr_JSAdditiv/Sine 5.wav + [data] tilr_JSAdditiv/Spectral.wav + [data] tilr_JSAdditiv/Square 1.wav + [data] tilr_JSAdditiv/Square 2.wav + [data] tilr_JSAdditiv/Stairs.wav + [data] tilr_JSAdditiv/Triangle.wav +screenshot: https://raw.githubusercontent.com/tiagolr/JSAdditiv/master/doc/ss.png +about: + # JSAdditiv + + Polyphonic additive synthesizer + + Features: + * Wavetable oscillator that reads single cycle waveforms from disk + * Additive synthesis engine that allows to modify the wave + * Wave editors for harmonics amplitude and phase + * 8 voices unison engine + * Pitch, amplitude and filter adsr + +desc: JSAdditiv +tags: synth, instrument + +slider1:/tilr_JSAdditiv:Sine 1.wav:Wave +slider2:vol=80<0, 100, .01>-Volume +slider4:_uni_voices=1<1, 8, 1>-Unison voices +slider5:uni_detune=15<0, 100, 0.1>-Unison detune +slider6:_uni_pan=50<0, 100, 0.1>-Unison panning + +slider7:osc_vel=100<0,100,0.1>-Velocity +slider8:osc_att=1<1, 10000, 1:log>-Attack +slider9:osc_dec=1<1, 10000, 1:log>-Decay +slider10:_osc_sus=100<0, 100, .1>-Sustain Db +slider11:osc_rel=500<1, 10000, 1:log>-Release + +slider13:flt_shape=0<0,3,1{Off,Low Pass,Band Pass,High Pass}>-Filter shape +slider14:flt_freq=22000<20, 22000, 1:log>-Filter frequency +slider15:flt_q=0.70<0.01, 40, 0.01:log>-Filter Q +slider16:flt_amt=0<-100, 100, 0.01>-Filter ASDR amt. +slider17:flt_att=1<1, 5000, 1:log>-Filter Attack +slider18:flt_dec=500<1, 5000, 1:log>-Filter Decay +slider19:_flt_sus=0<0, 100, 0.1>-Filter Sustain +slider20:flt_rel=500<1, 5000, 1:log>-Filter Release + +slider22:_pitch_amt=0<-24, 24, 1>-Pitch ASDR amt. +slider23:pitch_att=1<1, 5000, 1:log>-Pitch Attack +slider24:pitch_dec=500<1, 5000, 1:log>-Pitch Decay +slider25:_pitch_sus=0<0, 100, 0.1>-Pitch Sustain +slider26:pitch_rel=500<1, 5000, 1:log>-Pitch Release + +import add.wavetable.jsfx-inc +import add.array.jsfx-inc +import add.adsr.jsfx-inc +import add.zdf_filter.jsfx-inc +import add.gfxlib.jsfx-inc +import add.mouselib.jsfx-inc +import add.fft_real_synth.jsfx-inc + +options:gfx_hz=60 no_meter + +@init +wavebuf = 100000; +wavechn = 0; +wavelen = 0; +wavesrate = 0; +osc_free = 1; +env_panel = 0; +is_mono = 0; +edit_mode = 0; +phase_mode = 0; +harm_index = 0; // index of harmonics displayed, 0 displays first 34 harmonics + +poly.array_init(0, 128, 4+8); // [0note, 1velocity, 2freq, 3phase, 4..12 phase_unison] +envelope = 2000; // 129 * 7 buffer (where 129 buffer is the mono envelope) +pitch_env = 3000; // 128 * 7 buffer +filter_arr_l = 4000; // 128 * 11 buffer for filters one per key left channel +filter_arr_r = 6000; // 128 * 11 buffer for filters one per key right channel +filter_env = 8000; // 128 * 7 buffer for filter adsr +last_phase = 10000; // 9 buffer for copy pasting note osc phases + +function db2gain (db) local (val) ( + val = 10^(db / 20); + val <= 0.001 ? 0 : val; +); +function normalize_vol_slider(val) ( val * 60 / 100 - 60 ); +function note2freq(n) ( 440 * pow(2, (n - 69) / 12); ); + +function normalize_wave(buf, len) +local (_min, _max, i) +( + _min = 1; + _max = -1; + loop(i = 0; len, + _min = min(_min, buf[i]); + _max = max(_max, buf[i]); + i += 1; + ); + loop(i = 0; len, + buf[i] = 2 * ((buf[i] - _min) / (_max - _min)) -1; + i += 1; + ); +); + +/* + * Normalizes wave only if it passes max amplitude + */ +function semi_normalize_wave(buf, len) +local(_min, _max, i) +( + _min = 1; + _max = -1; + loop(i = 0; len, + _min = min(_min, buf[i]); + _max = max(_max, buf[i]); + i += 1; + ); + _min < -1 || _max > 1 ? ( + loop(i = 0; len, + buf[i] = 2 * ((buf[i] - _min) / (_max - _min)) -1; + i += 1; + ); + ) +); + +function stereo_to_mono_wave(buf, len) ( + loop(i = 0; (len / 2) | 0, + buf[i] = (buf[i*2] + buf[i*2+1]) * 0.5; + i += 1; + ); +); + +/* + * Convert wave into harmonics using FFT + */ +function synthesize(buf, len) +local (window) +( + window = 512 >= len ? 512 + : 1024 >= len ? 1024 + : 2048 >= len ? 2048 + : 4096; + four.four_init(buf, window); + four.four_fft(); +); + +/* + * Generate wavetable from FFT coeficients + */ +function resynthesize() ( + four.four_setf(1); + four.four_update(); + four.four_ifft(); + semi_normalize_wave(wavebuf, wavelen); // correct signal strenght after summing frequencies +); + +function on_file_change () ( + filehandle=file_open(slider1); + filehandle > 0 ? ( + file_riff(filehandle, wavechn, wavesrate); + wavechn ? ( + wavelen = file_avail(filehandle); + file_mem(filehandle,wavebuf,wavelen); + ); + file_close(filehandle); + wavechn == 2 ? ( + stereo_to_mono_wave(wavebuf, wavelen); + wavelen = (wavelen / 2) | 0; + ); + normalize_wave(wavebuf, wavelen); + osc.wave_init(wavebuf, wavelen); + synthesize(wavebuf, wavelen); + resynthesize(); + harm_index = 0; + ); +); + +// copy filter coeficients from buffer1 to buffer2 +function filter_copy_coefs(buf1, buf2) ( + buf2[2] = buf1[2]; + buf2[3] = buf1[3]; + buf2[4] = buf1[4]; +); + +// wraps filter function using buffers +function filter_setf(buf, freq, q) ( + filter.zdf_setf(freq, q); + buf[2] = filter.g; + buf[3] = filter.r2; + buf[4] = filter.h; +); + +// wraps filter function using buffers +function filter_lp(buf, sample) local(lp) ( + filter.s1 = buf[0]; + filter.s2 = buf[1]; + filter.g = buf[2]; + filter.r2 = buf[3]; + filter.h = buf[4]; + lp = filter.zdf_svf_lp(sample); + buf[0] = filter.s1; + buf[1] = filter.s2; + lp; +); + +// wraps filter function using buffers +function filter_bp(buf, sample) local(bp) ( + filter.s1 = buf[0]; + filter.s2 = buf[1]; + filter.g = buf[2]; + filter.r2 = buf[3]; + filter.h = buf[4]; + bp = filter.zdf_svf_bp(sample); + buf[0] = filter.s1; + buf[1] = filter.s2; + bp; +); + +// wraps filter function using buffers +function filter_hp(buf, sample) local(hp) ( + filter.s1 = buf[0]; + filter.s2 = buf[1]; + filter.g = buf[2]; + filter.r2 = buf[3]; + filter.h = buf[4]; + hp = filter.zdf_svf_hp(sample); + buf[0] = filter.s1; + buf[1] = filter.s2; + hp; +); + +function on_slider() ( + gain = db2gain(normalize_vol_slider(vol)); + lfile != slider1 ? on_file_change(); + uni_pan = _uni_pan / 100; + osc_sus = normalize_vol_slider(_osc_sus); + flt_sus = _flt_sus / 100; + uni_voices = _uni_voices | 0; + odd_voices = uni_voices & 1; + pitch_amt = _pitch_amt | 0; + pitch_sus = _pitch_sus / 100; + detune_amt = uni_detune / 100 * 3 / srate; // 3 hz max detune +); + +@serialize +file_var(0, osc_free); +file_mem(0, wavebuf, wavelen + four.size * 4); + +@slider + +on_slider(); + +@block + +// remove notes that finished adsr +ptr = poly.array_first(); +while (ptr >= 0) ( + env = envelope + ptr[0] * 7; + env[5] == 0 ? ( + poly.array_remove(ptr); + ptr = poly.array_first(); + ) : ( + ptr = poly.array_next(ptr); + ); +); + +while (midirecv(offset, msg1, note, vel)) ( + event = msg1 & 0xF0; + channel = msg1 & 0x0F; + + // Note on + event == 0x90 && vel ? ( + + // remove note if it is already playing + // copy its phase into new note to avoid clicking + note_repeat = 0; + ptr = poly.array_find(note); + ptr >= 0 ? ( + note_repeat = 1; + last_phase[0] = ptr[3]; + loop(i = 1; 8, + last_phase[i] = ptr[3+i]; + i += 1; + ); + poly.array_remove(ptr); + ); + + // if polyphony is full + poly.size == 127 ? ( + poly.array_remove(poly.array_first()); + ); + + ptr = poly.array_add(); + ptr[0] = note; + ptr[1] = vel / 127; + ptr[2] = note2freq(note) / srate; + ptr[3] = note_repeat ? last_phase[0] : osc_free ? rand(2) - 1 : 0; // osc start phase + loop(i = 4; 8, + ptr[i] = note_repeat ? last_phase[i-3] : osc_free ? rand(2) - 1 : 0; // unison start phase + i += 1; + ); + + env = envelope + note * 7; + adsr_seta(osc_att * 0.001, env); + adsr_setd(osc_dec * 0.001, env); + adsr_sets(exp(log(10)/20 * osc_sus), env); + adsr_setr(osc_rel * 0.001, env); + adsr_a(min(vel / 127 + (1 - osc_vel / 100), 1), env); + + env = pitch_env + note * 7; + adsr_seta(pitch_att * 0.001, env); + adsr_setd(pitch_dec * 0.001, env); + adsr_sets(pitch_sus, env); + adsr_setr(pitch_rel * 0.001, env); + adsr_a(1, env); + + env = filter_env + note * 7; + adsr_seta(flt_att * 0.001, env); + adsr_setd(flt_dec * 0.001, env); + adsr_sets(flt_sus, env); + adsr_setr(flt_rel * 0.001, env); + adsr_a(1, env); + ); + + // Note off + event == 0x80 || (event == 0x90 && !vel) ? ( + ptr = poly.array_find(note); + ptr >= 0 ? ( + adsr_r(envelope + note * 7); + adsr_r(pitch_env + note * 7); + adsr_r(filter_env + note * 7); + ); + ); + + // All notes off + event == 0xB0 && note == 123 ? ( + poly.array_clear(); + ); + + midisend(offset, msg1, note, vel); +); + +@sample + +ptr = poly.array_first(); +while(ptr >= 0) ( // for each note/voice + outl = 0; + outr = 0; + envbuf = envelope + ptr[0] * 7; + env_state = adsr_process(envbuf); + osc_pitch_det = 1; + pitch_amt ? ( + pitchbuf = pitch_env + ptr[0] * 7; + adsr_process(pitchbuf); + osc_pitch_det = pow(2, pitchbuf[0] * pitch_amt / 12); + ); + loop(i = 0; uni_voices, // for each unison voice + imod2 = i & 1; + pos = i == 0 && odd_voices ? 0 : imod2 ? -1 : 1; // center, left or right position + detune = !odd_voices + ? pos * detune_amt * (i + 1 - imod2) / uni_voices + : pos * detune_amt * (i + imod2) / uni_voices; + osc.wave_setdt(ptr[2] * osc_pitch_det + detune); + osc.wave_sync(ptr[i + 3]); + wave = osc.wave_spline3(); + pan = !odd_voices + ? pos * uni_pan * (i + 1 - imod2) / uni_voices + : pos * uni_pan * (i + imod2) / uni_voices; + outl += wave * (1 + pan) * envbuf[]; + outr += wave * (1 - pan) * envbuf[]; + ptr[i + 3] = osc.t; // update osc phase + i += 1; + ); + + // Apply filter + flt_shape != 0 ? ( + filterbuf = filter_env + ptr[0] * 7; // envelope buffer + adsr_process(filterbuf); + filterbuf_l = filter_arr_l + ptr[0] * 11; // filter buffer left + filterbuf_r = filter_arr_r + ptr[0] * 11; // filter buffer right + multiplier = pow(20000/flt_freq, filterbuf[0] * flt_amt / 100); + + filter_setf(filterbuf_l, flt_freq * multiplier, flt_q); + filter_copy_coefs(filterbuf_l, filterbuf_r); + + flt_shape == 1 ? ( + outl = filter_lp(filterbuf_l, outl); + outr = filter_lp(filterbuf_r, outr); + ) : flt_shape == 2 ? ( + outl = filter_bp(filterbuf_l, outl); + outr = filter_bp(filterbuf_r, outr); + ) : ( + outl = filter_hp(filterbuf_l, outl); + outr = filter_hp(filterbuf_r, outr); + ); + ); + + spl0 += outl * gain; + spl1 += outr * gain; + + ptr = poly.array_next(ptr); +); + +spl0 += outl * gain; +spl1 += outr * gain; + +@gfx 520 270 + +gfx_clear = COLOR_BG; +mouse.update_mouse_state(); + +draw_wave(20, 20, 120, 60, wavebuf, wavelen); + +//draw_grey_button(520 - 60, 2, 40, is_mono ? "Mono" : "Poly"); +//mouse.left_click && mouse_in_rect(520 - 60, 0, 40, 10 + 2) ? ( +// is_mono = !is_mono; +// on_slider(); +//); + +draw_button(160-2, 10-2, 50, "Harm", !phase_mode); +draw_button(160-2+50, 10-2, 50, "Phase", phase_mode); +mouse.left_click && mouse_in_rect(160-2, 10-2, 50, 12) ? phase_mode = 0; +mouse.left_click && mouse_in_rect(160-2+50, 10-2, 50, 12) ? phase_mode = 1; + +set_color(0xFFFFFFF); +harm_index > 0 ? ( + gfx_triangle(275, 10, 285, 5, 285, 15); + mouse.left_click && mouse_in_rect(275, 5, 10, 10) ? ( + harm_index -= 1; + ); +); +harm_index + 1< ceil(four.size / 34) ? ( + gfx_triangle(305, 10, 295, 5, 295, 15); + mouse.left_click && mouse_in_rect(295, 5, 10, 10) ? ( + harm_index += 1; + ); +); +gfx_x = 320; gfx_y = 10-2; +gfx_drawstr(sprintf(#, "%i / %i", harm_index + 1, + ceil(four.size / 34), 1, gfx_x+10, gfx_y + 10)); + +mouse.left_click && mouse_in_rect(160, 20, 340, 60) ? ( + edit_mode = 1; +); +edit_mode && !mouse.left ? ( + edit_mode = 0; + resynthesize(); +); +max_harm = harm_index + 1 >= ceil(four.size / 34) ? (harm_index + 1) * 34 - four.size - 1 : 34; +draw_harmonics(160, 20, 340, 60, four.coef+2 +harm_index*34*2, max_harm); + +draw_knob(20, 110, 2, "Vol", 80, 0, 100, 0, 0, sprintf(#, "%i%%", slider(2))); + +set_color(0x666666); +gfx_x = 160; gfx_y = 20+70; +gfx_drawstr("UNISON"); + +draw_button(220, 20+70, 40, "Free", osc_free); +mouse.left_click && mouse_in_rect(220, 20 - 2+70, 40, 10+2) ? ( + osc_free = !osc_free; +); + +draw_knob(160, 40+70, 4, "Voices", 1, 1, 8, 0, 0, sprintf(#, "%i", slider(4))); +draw_knob(220, 40+70, 5, "Det", 15, 0, 100, 0, 0, sprintf(#, "%i%%", slider(5))); +draw_knob(280, 40+70, 6, "Pan", 50, 0, 100, 0, 0, sprintf(#, "%i%%", slider(6))); + +set_color(0x666666); +gfx_x = 160; gfx_y = 110 + 70; +gfx_drawstr("ADSR"); + +draw_button(200, 110+70, 60, "Env", env_panel == 0); +mouse.left_click && mouse_in_rect(200, 110-2+70, 60, 14) ? env_panel = 0; +draw_button(260, 110+70, 60, "Pitch", env_panel == 1); +mouse.left_click && mouse_in_rect(260, 110-2+70, 60, 14) ? env_panel = 1; +draw_button(320, 110+70, 60, "Filter", env_panel == 2); +mouse.left_click && mouse_in_rect(320, 110-2+70, 60, 14) ? env_panel = 2; + +env_panel == 0 ? ( + draw_knob(160, 130+70, 7, "Vel", 0, 0, 100, 0, 0, sprintf(#, "%i%%", slider(7))); + draw_knob(220, 130+70, 8, "Att", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(8))); + draw_knob(280, 130+70, 9, "Dec", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(9))); + draw_knob(340, 130+70, 10, "Sus", 100, 0, 100, 0, 0, sprintf(#, "%i", slider(10))); + draw_knob(400, 130+70, 11, "Rel", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(11))); +); + +env_panel == 1 ? ( + draw_knob(160, 130+70, 22, "Amt", 0, -24, 24, 0, 1, sprintf(#, "%i", slider(22))); + draw_knob(220, 130+70, 23, "Att", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(23))); + draw_knob(280, 130+70, 24, "Dec", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(24))); + draw_knob(340, 130+70, 25, "Sus", 100, 0, 100, 0, 0, sprintf(#, "%i", slider(25))); + draw_knob(400, 130+70, 26, "Rel", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(26))); +); + +env_panel == 2 ? ( + draw_knob(160, 130+70, 16, "Amt", 0, -100, 100, 0, 1, sprintf(#, "%i%%", slider(16))); + draw_knob(220, 130+70, 17, "Att", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(17))); + draw_knob(280, 130+70, 18, "Dec", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(18))); + draw_knob(340, 130+70, 19, "Sus", 100, 0, 100, 0, 0, sprintf(#, "%i", slider(19))); + draw_knob(400, 130+70, 20, "Rel", 1, 1, 10000, 1, 0, sprintf(#, "%i ms", slider(20))); +); + +set_color(0x666666); +gfx_x = 400; gfx_y = 20+70; +gfx_drawstr("FILTER"); + +shape_name = flt_shape == 0 ? "Off" : flt_shape == 1 ? "LP" : flt_shape == 2 ? "BP" : "HP"; +draw_button(520 - 60, 20+70, 40, shape_name, 0); +mouse.left_click && mouse_in_rect(520-60, 20 - 2+70, 40, 10 + 2) ? ( + gfx_x = 520 - 60; gfx_y = 30+70; + choice = gfx_showmenu("Off|LP|BP|HP"); + choice > 0 ? slider(13) = choice - 1; + on_slider(); +); + +draw_knob(400, 40+70, 14, "Freq", 22000, 20, 22000, 1, 0, sprintf(#, "%i Hz", slider(14))); +draw_knob(460, 40+70, 15, "Q", 0.70, 0.01, 40, 1, 0, sprintf(#, "%.2f", slider(15))); + +function on_knob_move (nslider, slider_min, slider_max, slider_is_log, factor) ( + factor *= mouse.control ? 0.05 : 1; + slider_is_log ? ( + slider(nslider) = slider(nslider) * pow(100, -factor * 0.01); + ) : ( + inc = (slider_max - slider_min) / 100 * -factor; + slider(nslider) += inc; + ); + + slider(nslider) > slider_max ? slider(nslider) = slider_max; + slider(nslider) < slider_min ? slider(nslider) = slider_min; + on_slider(); + sliderchange(slider(nslider)); + slider_automate(slider(nslider)); +); + +wheelknob_nslider ? ( + on_knob_move(wheelknob_nslider, wheelknob_min, wheelknob_max, wheelknob_is_log, mouse.wheel * -7); +); + +selknob_nslider && mouse.left && mouse.dy != 0 ? ( + on_knob_move(selknob_nslider, selknob_min, selknob_max, selknob_is_log, mouse.dy); +); + +doubleclk_nslider ? ( + on_slider(); + sliderchange(slider(doubleclk_nslider)); + slider_automate(slider(doubleclk_nslider)); + doubleclk_nslider = 0; +); + +gfx_setcursor(selknob_nslider ? 32511 : 0); +!mouse.wheel ? wheelknob_nslider = 0; +!mouse.left ? selknob_nslider = 0; + diff --git a/Synth/tilr_JSAdditiv/Complex 1.wav b/Synth/tilr_JSAdditiv/Complex 1.wav new file mode 100644 index 0000000..a69e045 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Complex 1.wav differ diff --git a/Synth/tilr_JSAdditiv/Complex 2.wav b/Synth/tilr_JSAdditiv/Complex 2.wav new file mode 100644 index 0000000..5cf7701 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Complex 2.wav differ diff --git a/Synth/tilr_JSAdditiv/Complex 3.wav b/Synth/tilr_JSAdditiv/Complex 3.wav new file mode 100644 index 0000000..6faa068 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Complex 3.wav differ diff --git a/Synth/tilr_JSAdditiv/Complex 4.wav b/Synth/tilr_JSAdditiv/Complex 4.wav new file mode 100644 index 0000000..76d0ba6 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Complex 4.wav differ diff --git a/Synth/tilr_JSAdditiv/Complex 5.wav b/Synth/tilr_JSAdditiv/Complex 5.wav new file mode 100644 index 0000000..9001062 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Complex 5.wav differ diff --git a/Synth/tilr_JSAdditiv/Organ 1.wav b/Synth/tilr_JSAdditiv/Organ 1.wav new file mode 100644 index 0000000..557809f Binary files /dev/null and b/Synth/tilr_JSAdditiv/Organ 1.wav differ diff --git a/Synth/tilr_JSAdditiv/Organ 2.wav b/Synth/tilr_JSAdditiv/Organ 2.wav new file mode 100644 index 0000000..c28818d Binary files /dev/null and b/Synth/tilr_JSAdditiv/Organ 2.wav differ diff --git a/Synth/tilr_JSAdditiv/Organ 3.wav b/Synth/tilr_JSAdditiv/Organ 3.wav new file mode 100644 index 0000000..813cc62 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Organ 3.wav differ diff --git a/Synth/tilr_JSAdditiv/Organ 4.wav b/Synth/tilr_JSAdditiv/Organ 4.wav new file mode 100644 index 0000000..1599b12 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Organ 4.wav differ diff --git a/Synth/tilr_JSAdditiv/Organ 5.wav b/Synth/tilr_JSAdditiv/Organ 5.wav new file mode 100644 index 0000000..0d81532 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Organ 5.wav differ diff --git a/Synth/tilr_JSAdditiv/Organ 6.wav b/Synth/tilr_JSAdditiv/Organ 6.wav new file mode 100644 index 0000000..b3c0794 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Organ 6.wav differ diff --git a/Synth/tilr_JSAdditiv/Organ 7.wav b/Synth/tilr_JSAdditiv/Organ 7.wav new file mode 100644 index 0000000..28f2519 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Organ 7.wav differ diff --git a/Synth/tilr_JSAdditiv/Saw 1.wav b/Synth/tilr_JSAdditiv/Saw 1.wav new file mode 100644 index 0000000..3ad7e36 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Saw 1.wav differ diff --git a/Synth/tilr_JSAdditiv/Saw 2.wav b/Synth/tilr_JSAdditiv/Saw 2.wav new file mode 100644 index 0000000..883e637 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Saw 2.wav differ diff --git a/Synth/tilr_JSAdditiv/Saw 3.wav b/Synth/tilr_JSAdditiv/Saw 3.wav new file mode 100644 index 0000000..c54735d Binary files /dev/null and b/Synth/tilr_JSAdditiv/Saw 3.wav differ diff --git a/Synth/tilr_JSAdditiv/Sine 1.wav b/Synth/tilr_JSAdditiv/Sine 1.wav new file mode 100644 index 0000000..1846086 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Sine 1.wav differ diff --git a/Synth/tilr_JSAdditiv/Sine 2.wav b/Synth/tilr_JSAdditiv/Sine 2.wav new file mode 100644 index 0000000..00c6073 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Sine 2.wav differ diff --git a/Synth/tilr_JSAdditiv/Sine 3.wav b/Synth/tilr_JSAdditiv/Sine 3.wav new file mode 100644 index 0000000..1dc23ad Binary files /dev/null and b/Synth/tilr_JSAdditiv/Sine 3.wav differ diff --git a/Synth/tilr_JSAdditiv/Sine 4.wav b/Synth/tilr_JSAdditiv/Sine 4.wav new file mode 100644 index 0000000..840c6e9 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Sine 4.wav differ diff --git a/Synth/tilr_JSAdditiv/Sine 5.wav b/Synth/tilr_JSAdditiv/Sine 5.wav new file mode 100644 index 0000000..0596694 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Sine 5.wav differ diff --git a/Synth/tilr_JSAdditiv/Spectral.wav b/Synth/tilr_JSAdditiv/Spectral.wav new file mode 100644 index 0000000..d4d3b6a Binary files /dev/null and b/Synth/tilr_JSAdditiv/Spectral.wav differ diff --git a/Synth/tilr_JSAdditiv/Square 1.wav b/Synth/tilr_JSAdditiv/Square 1.wav new file mode 100644 index 0000000..3e3a25d Binary files /dev/null and b/Synth/tilr_JSAdditiv/Square 1.wav differ diff --git a/Synth/tilr_JSAdditiv/Square 2.wav b/Synth/tilr_JSAdditiv/Square 2.wav new file mode 100644 index 0000000..3df2d4b Binary files /dev/null and b/Synth/tilr_JSAdditiv/Square 2.wav differ diff --git a/Synth/tilr_JSAdditiv/Stairs.wav b/Synth/tilr_JSAdditiv/Stairs.wav new file mode 100644 index 0000000..01996df Binary files /dev/null and b/Synth/tilr_JSAdditiv/Stairs.wav differ diff --git a/Synth/tilr_JSAdditiv/Triangle.wav b/Synth/tilr_JSAdditiv/Triangle.wav new file mode 100644 index 0000000..3f6e9f0 Binary files /dev/null and b/Synth/tilr_JSAdditiv/Triangle.wav differ diff --git a/Synth/tilr_JSAdditiv/add.adsr.jsfx-inc b/Synth/tilr_JSAdditiv/add.adsr.jsfx-inc new file mode 100644 index 0000000..22ffd40 --- /dev/null +++ b/Synth/tilr_JSAdditiv/add.adsr.jsfx-inc @@ -0,0 +1,129 @@ +desc:ADSR envelope + +// Copyright (C) 2013-2017 Theo Niessink +// This work is free. You can redistribute it and/or modify it under the +// terms of the Do What The Fuck You Want To Public License, Version 2, +// as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. + +// Modified asrd lib to work with buffers instead + +// buf[0] = env +// buf[1] = a +// buf[2] = d +// buf[3] = s +// buf[4] = r +// buf[5] = state +// buf[6] = scale + +@init + +function _adsr_set(time) +( + 1 / (0.2 * time * srate + 1); +); + +function adsr_seta(time, buf) + //instance(a) +( + buf[1] = _adsr_set(time); +); + +function adsr_setd(time, buf) + //instance(d) +( + buf[2] = _adsr_set(time); +); + +function adsr_sets(gain, buf) + //instance(s) +( + buf[3] = gain; +); + +function adsr_setr(time, buf) + //instance(r) +( + buf[4] = _adsr_set(time); +); + +function adsr_reset(buf) + //instance(state, env) +( + buf[0] = 0; + buf[5] = 0; +); + +function adsr_r(buf) + // instance(state, r) +( + buf[4] < 1 ? ( // r < 1 ? state = 8 : adsr_reset() + buf[5] = 8; + ) : ( + adsr_reset(buf); + ); +); + +function adsr_s(buf) + // instance(state, s, env, scale) +( + buf[0] = buf[6] * buf[3]; // env = scale * s + buf[5] = 4; // state = 4 +); + +function adsr_d(buf) +// instance(state, d) +( + buf[2] < 1 ? ( // d < 1 + buf[5] = 2; // state = 2 + ) : ( + adsr_s(buf); + ); +); + +function adsr_a(scale, buf) + // instance(state, a, env) +( + buf[6] = scale; // this.scale = scale + + buf[1] < 1 ? ( // a < 1 + buf[5] = 1; // state = 1 + ) : ( + buf[0] = scale; // env = scale + adsr_d(buf); + ); +); + +function adsr_process(buf) +local (env, a, d, s, r, state, scale) + //instance(state, a, d, s, r, env, scale) +( + env = buf[0]; + a = buf[1]; + d = buf[2]; + s = buf[3]; + r = buf[4]; + state = buf[5]; + scale = buf[6]; + buf[5] ? ( + + // Decay + buf[5] == 2 ? ( + buf[0] += d * (scale * s - env); + abs(buf[0] - scale * s) <= scale * 0.000001 ? adsr_s(buf); + ) : + + // Release + buf[5] == 8 ? ( + buf[0] += r * (0 - env); + abs(buf[0]) <= scale * 0.000001 ? adsr_reset(buf); + ) : + + // Attack + buf[5] == 1 ? ( + buf[0] += a * (scale - env); + abs(buf[0] - scale) <= scale * exp(-5) ? adsr_d(buf); + ); + ); + + buf[5]; +); diff --git a/Synth/tilr_JSAdditiv/add.array.jsfx-inc b/Synth/tilr_JSAdditiv/add.array.jsfx-inc new file mode 100644 index 0000000..50876db --- /dev/null +++ b/Synth/tilr_JSAdditiv/add.array.jsfx-inc @@ -0,0 +1,269 @@ +desc:Simple two-dimensional array interface + +// Copyright (C) 2015-2019 Theo Niessink +// This work is free. You can redistribute it and/or modify it under the +// terms of the Do What The Fuck You Want To Public License, Version 2, +// as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. + +/* Example + + desc:Last-note priority mono synth + + import Tale/array.jsfx-inc + import Tale/midi_queue.jsfx-inc + import Tale/poly_blep.jsfx-inc + + @init + + voice.array_init(0, 128, 2); + + @sample + + while(midi.midiq_recv()) ( + midi.msg1 &= 0xF0; + + // Note On + midi.msg1 == 0x90 && midi.msg3 ? ( + + // Remove note if somehow it is already playing. + ptr = voice.array_find(midi.msg2); + ptr >= 0 ? voice.array_remove(ptr); + + // Add note, and set pointer to it. + ptr = voice.array_add(); + ptr[0] = midi.msg2; + + // Set oscillator frequency. + ptr[1] = osc.poly_setf(midi.msg2, 440); + osc.a *= 0.5; + ) : + + // Note Off + midi.msg1 == 0x80 || midi.msg1 == 0x90 ? ( + + // Remove note. + ptr = voice.array_find(midi.msg2); + ptr >= 0 ? ( + voice.array_remove(ptr); + !voice.size ? osc.a = 0 : ( + + // Update pointer to new last note. + ptr = voice.array_get(voice.size - 1); + osc.poly_setdt(ptr[1]); + osc.a *= 0.5; + ); + ); + ) : + + // All Notes Off + midi.msg1 == 0xB0 && midi.msg2 == 123 ? ( + voice.array_clear(); + ); + ); + + spl0 = spl1 = osc.poly_saw(); + + Initialization Functions + + * array_init(index, max_rows[, cols]) + Example: array.array_init(0, 64, 2); + Sets the offset and size of the local memory buffer to store the array + in, and returns the next available memory index (i.e. + index+rows*cols). If cols is omitted, then it defaults to 1. + + * array_alloc(max_rows[, cols]) + * array_free() + Example: array.array_alloc(64, 2); + Allocates/deallocates a block of local memory to store the array in, + and returns its index. + + Note: Requires Tale/malloc.jsfx-inc. + + Array Functions + + * array_get(row) + Example: ptr = array.array_get(0); + Returns a pointer to the local memory index of the specified row. + + * array_add() + Example: ptr = array.array_add(); + Adds a row to the end of the array and returns its local memory index. + Note that the row is added but not initialized (i.e. it does not + contain any data yet, nor is it zeroed.). + + * array_insert(ptr) + Example: array.array_insert(array.array_get(0)); + Inserts a row into the array. Note that the row is inserted but not + initialized. + + * array_remove(ptr) + Example: array.array_remove(array.array_get(0)); + Removes a row from the array. + + * array_clear() + Example: array.array_clear(); + Removes all rows from the array. + + Miscellaneous Functions + + * array_first() + Example: ptr = array.array_first(); + Returns a pointer to the local memory index of the first row, or -1 if + there are no rows. + + * array_next(ptr) + Example: ptr = array.array_next(ptr); + Returns a pointer to the local memory index of the next row, or -1 if + there is no next row. + + * array_last() + Example: ptr = array.array_last(); + Returns a pointer to the local memory index of the last row, or -1 if + there are no rows. + + * array_find(value[, col[, ptr]]) + Example: ptr = array_find(123); + Finds a value in the array at the specified column (0 by default), + starting at the specified row pointer (first row by default), and + returns the local memory index of the entire row, or -1 if the value + was not found. + + Instance Variables + + * buf + Example: ptr = array.buf; + The local memory address of the buffer in which the array is stored. + + * size + Example: num_rows = array.size; + The current size of the array in rows. + + * num + Example: num_cols = array.num; + The number of columns in each row. + +*/ + +@init + +function array_init(index, max_rows, cols) + instance(buf, size, num) +( + buf = index; + size = 0; + num = cols; + + buf + max_rows * num; +); + +function array_init(index, max_rows) +( + this.array_init(index, max_rows, 1); +); + +function array_get(row) + instance(buf, num) +( + buf + row * num; +); + +function array_add() + instance(buf, size, num) +( + buf + ((size += 1) - 1) * num; +); + +function array_insert(ptr) + instance(buf, size, num) + local(end) +( + end = buf + size * num; + size += 1; + ptr < end ? memcpy(ptr + num, ptr, end - ptr); + + // Returning the pointer here might not be very useful, but it is + // consistent with array_add(). + ptr; +); + +function array_remove(ptr) + instance(buf, size, num) + local(end) +( + end = buf + (size -= 1) * num; + ptr < end ? memcpy(ptr, ptr + num, end - ptr); + + // Again, returning the pointer here is not very useful; meh. + ptr; +); + +function array_first() + instance(buf, size) +( + size ? buf : -1; +); + +function array_next(ptr) + instance(buf, size, num) +( + ptr += num; + ptr < buf + size * num ? ptr : -1; +); + +function array_last() + instance(buf, size, num) +( + size ? buf + (size - 1) * num : -1; +); + +function array_find(value, col, ptr) + instance(buf, size, num) + local(ret, end) +( + ret = -1; + end = buf + size * num; + while( + ptr < end ? ( + ptr[col] == value ? ( + ret = ptr; + 0; // break + ) : ( + ptr += num; + 1; // continue + ); + ); + ); + ret; +); + +function array_find(value, col) + instance(buf) +( + this.array_find(value, col, buf); +); + +function array_find(value) + instance(buf, size, num) + local(ret, ptr, end) +( + ret = -1; + end = (ptr = buf) + size * num; + while( + ptr < end ? ( + ptr[] == value ? ( + ret = ptr; + 0; // break + ) : ( + ptr += num; + 1; // continue + ); + ); + ); + ret; +); + +function array_clear() + instance(size) +( + size = 0; +); diff --git a/Synth/tilr_JSAdditiv/add.fft_real_synth.jsfx-inc b/Synth/tilr_JSAdditiv/add.fft_real_synth.jsfx-inc new file mode 100644 index 0000000..31b9975 --- /dev/null +++ b/Synth/tilr_JSAdditiv/add.fft_real_synth.jsfx-inc @@ -0,0 +1,214 @@ +desc:Real FFT bandlimited synthesis + +// Copyright (C) 2015-2021 Theo Niessink +// This work is free. You can redistribute it and/or modify it under the +// terms of the Do What The Fuck You Want To Public License, Version 2, +// as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. + +/* Uses the real instead of the complex FFT, which is almost 2x as fast, but + requires REAPER v5.25+. See Tale/fft_synth.jsfx-inc for more + information. */ + +@init + +function four_real_align(index, size) +( + (index & 65535) + size > 65536 ? index = ((index >> 16) + 1) << 16 : index; +); + +function four_init(index, size) + instance(buf, coef, m) +( + m = 0; + + buf = four_real_align(index, size); + coef = four_real_align(buf + size * 2, size); + this.size = size; + + // Actually uses only up to coef+size, meh. + coef + size * 2; +); + +function four_setf(freq) + // global(srate) + instance(dt) +( + dt = freq / srate; +); + +function four_setf(note, tuning) + // global(srate) + instance(dt) +( + dt = exp(/* log(2)/12 */ 0.057762265046662109 * (note - 69)) * tuning / srate; +); + +function four_setdt(time) + instance(dt) +( + dt = time; +); + +function four_update() + instance(dt, size, m) + local(n) +( + dt > 0 ? n = min(ceil(0.5 / dt), size / 2) : n = 1; + n != m ? m = n; +); + +function four_update_odd() + instance(dt, size, m) + local(n) +( + dt > 0 ? n = min(ceil(0.5 / dt), size / 2) : n = 1; + (n|1) != (m|1) ? m = n : ( m = n; 0; ); +); + +function four_update_one() + instance(dt, size, m) + local(n) +( + dt > 0 ? n = min(ceil(0.5 / dt), size / 2) : n = 1; + (!n) != (!m) ? m = n : ( m = n; 0; ); +); + +function four_reset() + instance(m) +( + m = 0; +); + +function four_setdc(dc) + instance(coef) +( + coef[0] = dc; +); + +function four_setdc(dc, phase) + instance(coef) + local(t) +( + coef[0] = dc; + + t = phase + 0.5; + coef[1] = t - (t|0); + + coef + 2; +); + +function four_getdc() + instance(coef) +( + coef[0]; +); + +function four_getrms() + instance(coef, m) + local(sum, cos, sin) +( + sum = sqr(coef[0]) * 2; + + cos = coef + 2; + sin = cos + 1; + + loop(m - 1, + sum += sqr(cos[]) + sqr(sin[]); + cos += 2; sin += 2; + ); + + sqrt(sum * 0.5); +); + +function four_sum(phase) + instance(coef, m) + local(sum, k, cos, sin, t) +( + sum = coef[0]; + + t = coef[1] + phase; + t = 2*$pi * (t - (t|0)); + + cos = coef + 2; + sin = cos + 1; + + k = 1; + loop(m - 1, + sum += cos[] * cos(t * k) - sin[] * sin(t * k); + k += 1; cos += 2; sin += 2; + ); + + sum; +); + +function four_sigma(index, size) + local(ptr, x, dx, y) +( + // Skip DC. + ptr = index + 2; + + x = dx = size > 0 ? $pi / size; + loop(size - 1, + y = sin(x)/x; + x += dx; + ptr[0] *= y; + ptr[1] *= y; + ptr += 2; + ); +); + +function four_fft() + instance(buf, coef, size) + local(ptr, scale) +( + // Scale. + memcpy(ptr = coef, buf, size); + scale = 1 / size; + loop(size, + ptr[] *= scale; + ptr += 1; + ); + + fft_real(coef, size); + fft_permute(coef, size / 2); + + // Scale DC. + coef[0] *= 0.5; + // Zero phase offset. + coef[1] = 0; +); + +function four_ifft(sigma) + instance(buf, coef, size, m) + local(phase, ptr) +( + // Copy precalculated Fourier coefficients up to Nyquist. + memcpy(buf, coef, m * 2); + + // Decode phase offset. + phase = (buf[1] * size)|0; + buf[1] = 0; + + // Scale. + ptr = buf + 2; + loop((m - 1) * 2, + ptr[] *= 0.5; + ptr += 1; + ); + + sigma ? four_sigma(buf, m); + + // Zero bins beyond Nyquist frequency. + memset(buf + m * 2, 0, size - m * 2); + + fft_ipermute(buf, size / 2); + ifft_real(buf, size); + + phase > 0 ? ( + // Apply phase offset. + ptr = buf + size; + memcpy(ptr, buf, size); + memcpy(buf, ptr + phase, size - phase); + memcpy(ptr - phase, ptr, phase); + ); +); diff --git a/Synth/tilr_JSAdditiv/add.gfxlib.jsfx-inc b/Synth/tilr_JSAdditiv/add.gfxlib.jsfx-inc new file mode 100644 index 0000000..ee89f28 --- /dev/null +++ b/Synth/tilr_JSAdditiv/add.gfxlib.jsfx-inc @@ -0,0 +1,214 @@ +desc:add.gfxlib.jsfx-inc + +@init + +COLOR_ACTIVE = 0x5de4b1; +COLOR_BG = 0x141618; + +selknob_nslider = 0; +selknob_min = 0; +selknob_max = 0; +selknob_is_log = 0; + +wheelknob_nslider = 0; +wheelknob_min = 0; +wheelknob_max = 0; +wheelknob_is_log = 0; + +doubleclk_nslider = 0; + +function deg2rad (deg) (deg * $pi / 180;); +RAD130 = deg2rad(130); + + +function set_color(color) ( + gfx_r = (color & 0xFF0000) / 0xFF0000; + gfx_g = (color & 0x00FF00) / 0x00FF00; + gfx_b = (color & 0x0000FF) / 0x0000FF; +); + +function draw_wave(x, y, w, h, buf, len) +local(i, _x, _y) +( + set_color(COLOR_ACTIVE); + loop(i = 0; len, + _x = i * w / len + x; + _y = buf[i] * h / 2 + h / 2 + y; + i == 0 ? ( + gfx_x = _x; + gfx_y = _y; + ); + gfx_lineto(_x, _y); + i += 1; + ); +); + +function log_scale (value, max, min) +local (minP, maxP, scale) ( + minP = min; + maxP = max; + + minV = log(min); + maxV = log(max); + + scale = (maxV - minV) / (maxP - minP); + exp(minV + scale * (value - minP)); +); + +function inverse_log_scale (lg, max, min) +local (minP, maxP, scale) ( + minP = min; + maxP = max; + + minV = log(min); + maxV = log(max); + + scale = (maxV - minV) / (maxP - minP); + (log(lg) - minV) / scale + minP; +); + +function mouse_in_rect (x, y, w ,h) ( + mouse.x >= x && mouse.x <= x + w && mouse.y >= y && mouse.y <= y + h; +); + +function draw_knob(x, y, nslider, label, default, _min, _max, is_log, is_sym, val_label) +local (scale) +( + set_color(0x282D32); + gfx_arc(x+20, y+20, 20, -RAD130, RAD130, 1); + gfx_arc(x+20, y+20, 19.5, -RAD130, RAD130, 1); + gfx_arc(x+20, y+20, 19, -RAD130, RAD130, 1); + gfx_arc(x+20, y+20, 18.5, -RAD130, RAD130, 1); + + slider_val = slider(nslider); + nslider == 15 && lslider != slider_val ? ( + lsliderrrr += 1; + lsliderr = slider_val; + lsliderrr = lslider; + lslider = slider_val; + ); + is_log ? ( + slider_val = inverse_log_scale(slider_val, _max, _min); + ); + + // linear map value from min/max to -130deg +130deg + scale = (130 - -130) / (_max-_min); + _offset = (-_min * (130 - -130)) / (_max - _min) + -130; + slider_deg = slider_val * scale + _offset; + slider_rad = deg2rad(slider_deg); + + gfx_circle(x+20, y+20, 15, 1, 1); + set_color(COLOR_ACTIVE); + gfx_circle(x+20-sin(-slider_rad)*10, y+20-cos(-slider_rad)*10, 3, 1); + + gfx_arc(x+20, y+20, 20, is_sym ? 0 : -RAD130, slider_rad, 1); + gfx_arc(x+20, y+20, 19.5, is_sym ? 0 : -RAD130, slider_rad, 1); + gfx_arc(x+20, y+20, 19, is_sym ? 0 : -RAD130, slider_rad, 1); + gfx_arc(x+20, y+20, 18.5, is_sym ? 0 : -RAD130, slider_rad, 1); + + set_color(0xFFFFFF); + gfx_x = x - 20; + gfx_y = y + 20 * 2 + 5; + selknob_nslider == nslider ? ( + gfx_drawstr(val_label, 1, x+20+20*2, y+100); + ) : ( + gfx_drawstr(label, 1, x+20+20*2, y+100); + ); + + mouse_in_rect(x, y, 40, 40) ? ( + mouse.double_click ? ( + slider(nslider) = default; + doubleclk_nslider = nslider; + ); + mouse.left_click ? ( + selknob_nslider = nslider; + selknob_min = _min; + selknob_max = _max; + selknob_is_log = is_log; + ); + mouse.wheel ? ( + wheelknob_nslider = nslider; + wheelknob_min = _min; + wheelknob_max = _max; + wheelknob_is_log = is_log; + ); + ); +); + +function draw_button (x, y, w, label, toggled) ( + set_color(COLOR_ACTIVE); + gfx_rect(x, y - 2, w, 10 + 2); + gfx_x = x; gfx_y = y; + !toggled ? ( + set_color(COLOR_BG); + gfx_rect(x+1, y+1-2, w-2, 10); + ); + set_color(toggled ? 0xFFFFFF : COLOR_ACTIVE); + gfx_drawstr(label, 1, x+w, y+10); +); + +function draw_grey_button (x, y, w, label) ( + set_color(0x666666); + gfx_rect(x, y - 2, w, 10 + 2); + gfx_x = x; gfx_y = y; + set_color(0x141618); + gfx_drawstr(label, 1, x+w, y+10); +); + +function harmonic_phase(h1, h2) ( + atan2(h1, h2); +); + +function harmonic_amplitude(h1, h2) ( + sqrt(pow(h1,2) + pow(h2,2)); +); + +function draw_harmonics(x, y, w, h, buf, nharm) +local (harm_w, amp, i, mouse_active, hx, ph, yamp, phase, real, img, yphase) +( + set_color(0x666666); + gfx_rect(x - 2,y - 2,w + 4,h + 4); + set_color(COLOR_BG); + gfx_rect(x - 1, y - 1, w + 2, h + 2); + set_color(COLOR_ACTIVE); + harm_w = (w / nharm) | 0; + mouse_active = edit_mode && mouse.left && mouse_in_rect(x,y,w,h); + + phase_mode ? gfx_a = 0.3; + loop(i=0; nharm, + amp = harmonic_amplitude(buf[i], buf[i+1]); + amp > 1 ? amp = 1; + hx = x + harm_w * i / 2; // harmonic x + gfx_rect(hx, y + h - floor(amp * h), harm_w - 1, amp * h); + !phase_mode && mouse_active && mouse_in_rect(hx, y, harm_w, h) ? ( + yamp = mouse.control ? 0 : 1 - (mouse.y - y) / h; + phase = harmonic_phase(buf[i], buf[i+1]); + buf[i+1] = yamp * cos(phase); // real = r * cos(p) + buf[i] = yamp * sin(phase); // img = r * sin(p) + ); + i += 2; + ); + gfx_a = 0.75; + set_color(0xFFFFFF); + phase_mode ? ( + loop(i=0; nharm, + phase = atan2(buf[i], buf[i+1]); + hx = x + harm_w * i / 2; + ph = phase/$pi*h/2; // phase height + phase >= 0 ? ( + gfx_rect(hx, y + h/2 - ph, harm_w-1, max(ph, 1)|0); + ) : ( + gfx_rect(hx, y + h/2, harm_w-1, max(-ph, 1)|0); + ); + phase_mode && mouse_active && mouse_in_rect(hx, y, harm_w, h) ? ( + yphase = mouse.control ? 0 : (mouse.y - y) / h * -2 - 1; + yphasee = yphase; + amp = harmonic_amplitude(buf[i], buf[i+1]); + buf[i+1] = amp * cos(yphase*$pi); // real + buf[i] = amp * sin(yphase*$pi); // img + ); + i += 2; + ); + ); + gfx_a = 1; +); diff --git a/Synth/tilr_JSAdditiv/add.mouselib.jsfx-inc b/Synth/tilr_JSAdditiv/add.mouselib.jsfx-inc new file mode 100644 index 0000000..1c846fb --- /dev/null +++ b/Synth/tilr_JSAdditiv/add.mouselib.jsfx-inc @@ -0,0 +1,34 @@ +desc:add.mouselib.jsfx-inc + +@init + +function update_mouse_state() +instance(cap, x, y, lx, ly, dx, dy, right_click, left_click, lleft, lright, left, right, click_time, double_click, control, lwheel, wheel) +global(mouse_cap, mouse_x, mouse_y, mouse_wheel) +( + lleft = left; + lright = right; + lx = x; + ly = y; + cap = mouse_cap; + control = mouse_cap & 4; + x = mouse_x; + y = mouse_y; + + left = cap & 1 > 0; + right = cap & 2 > 0; + left_click = left && lleft == 0; + right_click = right && lright == 0; + dx = x - lx; + dy = y - ly; + + wheel = mouse_wheel > lwheel ? 1 : mouse_wheel < lwheel ? -1 : 0; + lwheel = mouse_wheel; + + left_click ? ( + time_precise() - click_time < .5 ? double_click = 1; + click_time = time_precise(); + ) : ( + double_click = 0; + ); +); diff --git a/Synth/tilr_JSAdditiv/add.wavetable.jsfx-inc b/Synth/tilr_JSAdditiv/add.wavetable.jsfx-inc new file mode 100644 index 0000000..2b38036 --- /dev/null +++ b/Synth/tilr_JSAdditiv/add.wavetable.jsfx-inc @@ -0,0 +1,595 @@ +desc:Wavetable oscillator + +// Copyright (C) 2015-2017 Theo Niessink +// This work is free. You can redistribute it and/or modify it under the +// terms of the Do What The Fuck You Want To Public License, Version 2, +// as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. + +/* Example + + desc:Sine wave wavetable oscillator + + slider1:440<20,20000,1>Freq (Hz) + slider2:1<0,2,1{Truncate,Linear,Spline}>Mode + + import Tale/wavetable.jsfx-inc + + @init + + function render_sin(buf, size, gain) + local(x, dx) + ( + x = 0; + dx = 2*$pi / size; + loop(size, + buf[] = gain * sin(x); + buf += 1; + x += dx; + ); + ); + + osc.wave_init(0, 16); + render_sin(osc.buf, osc.size, 0.25); + + @slider + + osc.wave_setf(slider1); + mode = slider2|0; + + @sample + + spl0 = spl1 = + mode == 0 ? osc.wave_trunc() : + mode == 1 ? osc.wave_lerp() : + mode == 2 ? osc.wave_spline3(); + + Initialisation Functions + + * wave_init(index, size) + Example: osc.wave_init(0, 1024); + Sets the offset and size of the local memory buffer that will hold the + waveform, and returns the next available memory index (i.e. + index+size). + + * wave_alloc(size) + * wave_free() + Example: osc.wave_alloc(1024); + Allocates/deallocates a block of local memory that will hold the + waveform, and returns its index. + + Note: Requires malloc.jsfx-inc. + + Setting Functions + + * wave_setf(freq) + Example: osc.wave_setf(440); + Sets the oscillator frequency (specified in Hz), and returns the + frequency in seconds/sample. + + (To convert from Hz to seconds/sample, divide by srate. To convert + from seconds/sample to Hz, multiply with srate.) + + * wave_setf(note, tuning) + Example: osc.wave_setf(60, 440); + Sets the oscillator frequency to the specified MIDI note and tuning + (in Hz), and returns the frequency in seconds/sample. + + * wave_setdt(time) + Example: osc2.wave_setdt(osc1.dt); + Sets the oscillator frequency (specified in seconds/sample), and + returns this value. + + Interpolation Functions + + * wave_trunc() -- Truncate + * wave_round() -- Round to nearest + * wave_lerp() -- Linear + + * wave_hermite3() -- Catmull-Rom spline (4-point, 3rd-order) + * wave_hermite5() -- Hermite (6-point, 5th-order) + + * wave_quint() -- Quintic spline (6-point, 5th-order) + + * wave_spline3() -- B-spline (4-point, 3rd-order) + * wave_spline5() -- B-spline (6-point, 5th-order) + + * wave_osculate3() -- 2nd-order-osculating (4-point, 5th-order) + * wave_osculate5() -- 2nd-order-osculating (6-point, 5th-order) + + * wave_lagrange2() -- Lagrange (3-point, 2nd-order) + * wave_lagrange3() -- Lagrange (4-point, 3rd-order) + * wave_lagrange4() -- Lagrange (5-point, 4th-order) + * wave_lagrange5() -- Lagrange (6-point, 5th-order) + * wave_lagrange(n) -- Lagrange (N+1-point, Nth-order) + Example: sample = osc.wave_lerp(); + Returns a sample from the wavetable, and increments the oscillator + phase. + + Miscellaneous Functions + + * wave_sync(phase) + Example: osc2.wave_sync(osc1.t + 0.5); + Synchronizes the oscillator with the specified phase, and returns the + normalized phase. + + Note: You can safely specify out or range (and even negative) values + here. + + * wave_inc() + Example: osc.wave_inc(); + Increments the oscillator phase, and returns it. + + Note: The interpolation functions automatically increment the phase. + + * wave_getdc() + * wave_getrms() + Example: dc = osc.wave_getdc(); + Calculates the DC/RMS value of the waveform. + + Instance Variables + + * buf + Example: wavetbl_index = osc.buf; + The local memory index of the wavetable. + + * size + Example: wavetbl_size = osc.size; + The size of the wavetable, in samples. + + * t + Example: phase = osc.t; + The current phase [0.0..1.0) of the oscillator. + + * dt + Example: freq = osc.dt * srate; + The oscillator frequency, in seconds/sample. + +*/ + +@init + +function wave_init(index, size) + instance(buf) +( + buf = index; + this.size = size; + + buf + size; +); + +function wave_setf(freq) + // global(srate) + instance(dt) +( + dt = freq / srate; +); + +function wave_setf(note, tuning) + // global(srate) + instance(dt) +( + dt = exp(/* log(2)/12 */ 0.057762265046662109 * (note - 69)) * tuning / srate; +); + +function wave_setdt(time) + instance(dt) +( + dt = time; +); + +function wave_sync(phase) + instance(t) +( + t = phase; + t >= 0 ? t -= t|0 : t += 1 - (t|0); +); + +function wave_inc() + instance(t, dt) +( + t += dt; + t -= t|0; +); + +function wave_getdc() + instance(buf, size) + local(sum, ptr) +( + sum = 0; + ptr = buf; + loop(size, + sum += ptr[]; + ptr += 1; + ); + sum / size; +); + +function wave_getrms() + instance(buf, size) + local(sum, ptr) +( + sum = 0; + ptr = buf; + loop(size, + sum += sqr(ptr[]); + ptr += 1; + ); + sqrt(sum / size); +); + +// Truncate + +function wave_trunc() + instance(buf, size, t) + local(i) +( + i = (t * size)|0; + this.wave_inc(); + + buf[i]; +); + +// Round to nearest + +function wave_round() + instance(buf, size, t) + local(i) +( + i = (t * size + 0.5)|0; + this.wave_inc(); + + i >= size ? i = 0; + buf[i]; +); + +// Linear + +function wave_lerp() + instance(buf, size, t) + local(x, i, j) +( + x = t * size; + this.wave_inc(); + + i = x|0; + x -= i; + + j = i + 1; + j >= size ? j = 0; + + (1 - x) * buf[i] + x * buf[j]; +); + +// Catmull-Rom spline (4-point, 3rd-order) + +function wave_hermite3() + instance(buf, size, t) + local(x, i, j, y0, y1, y2, y3, a0, a1, a2, a3) +( + x = t * size; + this.wave_inc(); + + i = x|0; + x -= i; + + j = i - 1; j < 0 ? j += size; y0 = buf[j]; + y1 = buf[i]; + i += 1; i >= size ? i = 0; y2 = buf[i]; + i += 1; i >= size ? i = 0; y3 = buf[i]; + + a0 = 0.5*(y3 - y0) + 1.5*(y1 - y2); + a1 = y0 - 2.5*y1 + 2*y2 - 0.5*y3; + a2 = 0.5*(y2 - y0); + a3 = y1; + + // x^3*a0 + x^2*a1 + x*a2 + a3 + x*(x*(x*a0 + a1) + a2) + a3; +); + +// Hermite (6-point, 5th-order) x-form adapted from +// http://www.student.oulu.fi/~oniemita/dsp/deip.pdf + +function wave_hermite5() + instance(buf, size, t) + local(x, i, j, y0, y1, y2, y3, y4, y5, a0, a1, a2, a3, a4, a5) +( + x = t * size; + this.wave_inc(); + + i = x|0; + x -= i; + + j = i - 2; j < 0 ? j += size; y0 = buf[j]; + j += 1; j >= size ? j = 0; y1 = buf[j]; + y2 = buf[i]; + i += 1; i >= size ? i = 0; y3 = buf[i]; + i += 1; i >= size ? i = 0; y4 = buf[i]; + i += 1; i >= size ? i = 0; y5 = buf[i]; + + a0 = 1/24*(y5 - y0) + 5/24*(y1 - y4) + 5/12*(y3 - y2); + a1 = 0.125*y0 - 7/12*y1 + 13/12*y2 - y3 + 11/24*y4 - 1/12*y5; + a2 = 5/12*y2 - 7/12*y3 + 7/24*y4 - 1/24*(y0 + y1 + y5); + a3 = 13/12*y1 - 25/12*y2 + 1.5*y3 - 11/24*y4 + 1/12*y5 - 0.125*y0; + a4 = 1/12*(y0 - y4) + 2/3*(y3 - y1); + a5 = y2; + + x*(x*(x*(x*(x*a0 + a1) + a2) + a3) + a4) + a5; +); + +// Quintic spline (6-point, 5th-order) from +// http://musicdsp.org/archive.php?classid=5#60 + +function wave_quint() + instance(buf, size, t) + local(x, i, j, y0, y1, y2, y3, y4, y5) +( + x = t * size; + this.wave_inc(); + + i = x|0; + x -= i; + + j = i - 2; j < 0 ? j += size; y0 = buf[j]; + j += 1; j >= size ? j = 0; y1 = buf[j]; + y2 = buf[i]; + i += 1; i >= size ? i = 0; y3 = buf[i]; + i += 1; i >= size ? i = 0; y4 = buf[i]; + i += 1; i >= size ? i = 0; y5 = buf[i]; + + y2 + 1/24*x*((y3-y1)*16+(y0-y4)*2 + + x *((y3+y1)*16-y0-y2*30- y4 + + x *(y3*66-y2*70-y4*33+y1*39+ y5*7- y0*9 + + x *( y2*126-y3*124+y4*61-y1*64- y5*12+y0*13 + + x *((y3-y2)*50+(y1-y4)*25+(y5-y0)*5))))); +); + +// B-spline (4-point, 3rd-order) x-form adapted from +// http://www.student.oulu.fi/~oniemita/dsp/deip.pdf + +function wave_spline3() + instance(buf, size, t) + local(x, i, j, y0, y1, y2, y3, a0, a1, a2, a3) +( + x = t * size; + this.wave_inc(); + + i = x|0; + x -= i; + + j = i - 1; j < 0 ? j += size; y0 = buf[j]; + y1 = buf[i]; + i += 1; i >= size ? i = 0; y2 = buf[i]; + i += 1; i >= size ? i = 0; y3 = buf[i]; + + a0 = 0.5*(y1 - y2) + 1/6*(y3 - y0); + a1 = 0.5*(y0 + y2) - y1; + a2 = 0.5*(y2 - y0); + a3 = 1/6*(y0 + y2) + 2/3*y1; + + x*(x*(x*a0 + a1) + a2) + a3; +); + +// B-spline (6-point, 5th-order) x-form adapted from +// http://www.student.oulu.fi/~oniemita/dsp/deip.pdf + +function wave_spline5() + instance(buf, size, t) + local(x, i, j, y0, y1, y2, y3, y4, y5, a0, a1, a2, a3, a4, a5) +( + x = t * size; + this.wave_inc(); + + i = x|0; + x -= i; + + j = i - 2; j < 0 ? j += size; y0 = buf[j]; + j += 1; j >= size ? j = 0; y1 = buf[j]; + y2 = buf[i]; + i += 1; i >= size ? i = 0; y3 = buf[i]; + i += 1; i >= size ? i = 0; y4 = buf[i]; + i += 1; i >= size ? i = 0; y5 = buf[i]; + + a0 = 1/120*(y5 - y0) + 1/24*(y1 - y4) + 1/12*(y3 - y2); + a1 = 1/24*(y0 + y4) - 1/6*(y1 + y3) + 0.25*y2; + a2 = 1/12*(y4 - y0) - 1/6*(y3 - y1); + a3 = 1/12*(y0 + y4) + 1/6*(y1 + y3) - 0.5*y2; + a4 = 1/24*(y4 - y0) + 5/12*(y3 - y1); + a5 = 1/120*(y0 + y4) + 13/60*(y1 + y3) + 11/20*y2; + + x*(x*(x*(x*(x*a0 + a1) + a2) + a3) + a4) + a5; +); + +// 2nd-order-osculating (4-point, 5th-order) x-form adapted from +// http://www.student.oulu.fi/~oniemita/dsp/deip.pdf + +function wave_osculate3() + instance(buf, size, t) + local(x, i, j, y0, y1, y2, y3, a0, a1, a2, a3, a4, a5) +( + x = t * size; + this.wave_inc(); + + i = x|0; + x -= i; + + j = i - 1; j < 0 ? j += size; y0 = buf[j]; + y1 = buf[i]; + i += 1; i >= size ? i = 0; y2 = buf[i]; + i += 1; i >= size ? i = 0; y3 = buf[i]; + + a0 = y0 + 3*(y2 - y1) - y3; + a1 = 2.5*(y3 - y0) - 7.5*(y2 - y1); + a2 = 4.5*(y2 - y1) - 1.5*(y3 - y0); + a3 = 0.5*(y0 + y2) - y1; + a4 = 0.5*(y2 - y0); + a5 = y1; + + x*(x*(x*(x*(x*a0 + a1) + a2) + a3) + a4) + a5; +); + +// 2nd-order-osculating (6-point, 5th-order) x-form adapted from +// http://www.student.oulu.fi/~oniemita/dsp/deip.pdf + +function wave_osculate5() + instance(buf, size, t) + local(x, i, j, y0, y1, y2, y3, y4, y5, a0, a1, a2, a3, a4, a5) +( + x = t * size; + this.wave_inc(); + + i = x|0; + x -= i; + + j = i - 2; j < 0 ? j += size; y0 = buf[j]; + j += 1; j >= size ? j = 0; y1 = buf[j]; + y2 = buf[i]; + i += 1; i >= size ? i = 0; y3 = buf[i]; + i += 1; i >= size ? i = 0; y4 = buf[i]; + i += 1; i >= size ? i = 0; y5 = buf[i]; + + a0 = 5/24*(y5 - y0) + 25/24*(y1 - y4) + 25/12*(y3 - y2); + a1 = 13/24*y0 - 8/3*y1 + 5.25*y2 - 31/6*y3 + 61/24*y4 - 0.5*y5; + a2 = 1.625*y1 - 35/12*y2 + 2.75*y3 - 1.375*y4 + 7/24*y5 - 0.375*y0; + a3 = 2/3*(y1 + y3) - 1/24*(y0 + y4) - 1.25*y2; + a4 = 1/12*(y0 - y4) + 2/3*(y3 - y1); + a5 = y2; + + x*(x*(x*(x*(x*a0 + a1) + a2) + a3) + a4) + a5; +); + +// Lagrange (3-point, 2nd-order) + +function wave_lagrange2() + instance(buf, size, t) + local(x, h, i, j) +( + x = t * size; + this.wave_inc(); + + i = x|0; + x -= i; + + h = i - 1; h < 0 ? h += size; + j = i + 1; j >= size ? j = 0; + + 0.5 * x * (x - 1) * buf[h] - + (x + 1) * (x - 1) * buf[i] + + 0.5 * (x + 1) * x * buf[j]; +); + +// Lagrange (4-point, 3rd-order) + +function wave_lagrange3() + instance(buf, size, t) + local(x, h, i, j, k) +( + x = t * size; + this.wave_inc(); + + i = x|0; + x -= i; + + h = i - 1; h < 0 ? h += size; + j = i + 1; j >= size ? j = 0; + k = j + 1; k >= size ? k = 0; + + -1/6 * x * (x - 1) * (x - 2) * buf[h] + + 0.5 * (x + 1) * (x - 1) * (x - 2) * buf[i] - + 0.5 * (x + 1) * x * (x - 2) * buf[j] - + -1/6 * (x + 1) * x * (x - 1) * buf[k]; +); + +// Lagrange (5-point, 4th-order) + +function wave_lagrange4() + instance(buf, size, t) + local(x, g, h, i, j, k) +( + x = t * size; + this.wave_inc(); + + i = x|0; + x -= i; + + g = i - 2; g < 0 ? g += size; + h = g + 1; h >= size ? h = 0; + j = i + 1; j >= size ? j = 0; + k = j + 1; k >= size ? k = 0; + + 1/24 * (x + 1) * x * (x - 1) * (x - 2) * buf[g] - + 1/6 * (x + 2) * x * (x - 1) * (x - 2) * buf[h] + + 0.25 * (x + 2) * (x + 1) * (x - 1) * (x - 2) * buf[i] - + 1/6 * (x + 2) * (x + 1) * x * (x - 2) * buf[j] + + 1/24 * (x + 2) * (x + 1) * x * (x - 1) * buf[k]; +); + +// Lagrange (6-point, 5th-order) + +function wave_lagrange5() + instance(buf, size, t) + local(x, g, h, i, j, k, l) +( + x = t * size; + this.wave_inc(); + + i = x|0; + x -= i; + + g = i - 2; g < 0 ? g += size; + h = g + 1; h >= size ? h = 0; + j = i + 1; j >= size ? j = 0; + k = j + 1; k >= size ? k = 0; + l = k + 1; l >= size ? l = 0; + + -1/120 * (x + 1) * x * (x - 1) * (x - 2) * (x - 3) * buf[g] + + 1/24 * (x + 2) * x * (x - 1) * (x - 2) * (x - 3) * buf[h] - + 1/12 * (x + 2) * (x + 1) * (x - 1) * (x - 2) * (x - 3) * buf[i] + + 1/12 * (x + 2) * (x + 1) * x * (x - 2) * (x - 3) * buf[j] - + 1/24 * (x + 2) * (x + 1) * x * (x - 1) * (x - 3) * buf[k] - + -1/120 * (x + 2) * (x + 1) * x * (x - 1) * (x - 2) * buf[l]; +); + +// Lagrange (N+1-point, Nth-order) + +function wave_lagrange(n) + instance(buf, size, t) + local(x, i, j, m, ofs, sum, mul) +( + x = t * size; + this.wave_inc(); + + i = x|0; + x -= i; + + ofs = (n / 2)|0; + i -= ofs; i < 0 ? i += size; + x += ofs; + + sum = j = 0; + loop(n + 1, + mul = buf[i]; + i += 1; i >= size ? i = 0; + + m = 0; + loop(n, + m == j ? m += 1; + mul *= (x - m) / (j - m); + m += 1; + ); + sum += mul; + j += 1; + ); + sum; +); + +// Legacy + +function wave_cube() ( this.wave_hermite3() ); + +// Deprecated + +function wave_cube6() ( this.wave_hermite5() ); +function wave_cube8() ( this.wave_hermite5() ); + +function wave_quint6() ( this.wave_quint() ); +function wave_quint8() ( this.wave_quint() ); diff --git a/Synth/tilr_JSAdditiv/add.zdf_filter.jsfx-inc b/Synth/tilr_JSAdditiv/add.zdf_filter.jsfx-inc new file mode 100644 index 0000000..48c4b58 --- /dev/null +++ b/Synth/tilr_JSAdditiv/add.zdf_filter.jsfx-inc @@ -0,0 +1,403 @@ +desc:2nd-order zero-delay feedback state variable filter + +// Copyright (C) 2013-2021 Theo Niessink +// This work is free. You can redistribute it and/or modify it under the +// terms of the Do What The Fuck You Want To Public License, Version 2, +// as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. + +// Initially based on the rsStateVariableFilter C++ class by Robin Schmidt, +// as posted (in the public domain) on the KVR forum. +// http://www.kvraudio.com/forum/viewtopic.php?p=5243733#p5243733 + +/* Example + + desc:Low-pass filter + + slider1:1000<20,20000,1>Cutoff (Hz) + slider2:0.5<0.01,4.0,0.01>Q + + import Tale/zdf_filter.jsfx-inc + + @slider + lp.zdf_lp(slider1, slider2); + lp.zdf_gain(0.5); + + @sample + spl0 = spl1 = lp.zdf_svf(spl0 + spl1); + + Setting Functions + + * zdf_lp(freq, q) -- Low-pass + * zdf_hp(freq, q) -- High-pass + * zdf_bp(freq, q) -- Band-pass (constant skirt gain) + * zdf_bp2(freq, q) -- Band-pass (constant peak gain) + * zdf_bs(freq, q) -- Band-stop + * zdf_ap(freq, q) -- All-pass + * zdf_eq(freq, q, gain) -- Peaking EQ + * zdf_ls(freq, q, gain) -- Low-shelf + * zdf_hs(freq, q, gain) -- High-shelf + Example: lp.zdf_lp(1000, 0.7); + Sets up the filter for the specified cutoff frequency (in Hz), and Q + and gain factors, and returns the feedback precomputation factor (h). + + (To convert from dB to gain: gain=10^(db/20).) + + Note: In v20151024 the behavior of zdf_bp2() and zdf_ap() has been + changed in such a way that these functions are not backward + compatible. To convert code relying on the old behavior, replace + zdf_bp2(freq, bw) with zdf_bp(freq, zdf_bwtoq(bw)), and + zdf_ap(freq, bw) with zdf_ap(freq, zdf_bwtoq(bw)). + + * zdf_gain(gain) + Example: lp.zdf_lp(1000, 0.5); lp.zdf_gain(2.0); + Modifies the filter by applying the specified output gain. + + Note: You should always first setup the filter, and then modify it. If + you change the filter frequency/Q afterwards, then this will reset the + gain to 1.0, and so you will have to modify it again. + + * zdf_setf(freq, q) + Example: lp.zdf_setf(1000, 0.7); + Sets up the specialized low-pass, high-pass, or band-pass filter. + + Note: This works only with zdf_svf_lp(), zdf_svf_hp(), or zdf_svf_bp(). + + Filter Functions + + * zdf_svf(sample) + Example: output = lp.zdf_svf(input); + Sends a sample through the filter, and returns its output. + + * zdf_svf_multi(sample) + Example: output = lp.zdf_svf_multi(input); + Sends a sample through the filter, returns its output, and also stores + the individual low-pass, band-pass, and high-pass outputs. + + * zdf_svf_lp(sample) -- Low-pass + * zdf_svf_hp(sample) -- High-pass + * zdf_svf_bp(sample) -- Band-pass + Example: output = lp.zdf_svf_lp(input); + Specialized versions of zdf_svf(), each optimized for a specific + filter type. + + Miscellaneous Functions + + * zdf_reset([input]) + Example: lp.zdf_reset(); + Resets the filter state to the specified input value, or to zero if + the value is omitted. + + * zdf_bwtoq(bw) + * zdf_qtobw(q) + Example: q = zdf_bwtoq(2.0); + Converts bandwidth (in octaves) to Q factor, or vice versa. + + Instance Variables + + * g -- Embedded integrator gain + * r2 -- Damping (1/Q) + * h -- Feedback precomputation factor + Example: lp2.g = lp1.g; lp2.r2 = lp1.r2; lp2.h = lp1.h; + Filter coefficients. + + * cl -- Low-pass mix + * cb -- Band-pass mix + * ch -- High-pass mix + Example: lp2.cl = lp1.cl; lp2.cb = lp1.cb; lp2.ch = lp1.ch; + Filter mode output mix. + + * s1 + * s2 + Example: lp2.s1 = lp1.s1; lp2.s2 = lp1.s2; + Filter state. + + * yl -- Low-pass output + * yb -- Band-pass output + * yh -- High-pass output + Example: hp = lp.yh; + Multi-mode filter outputs. + +*/ + +@init + +function zdf_bwtoq(bw) + local(x) +( + // q = 1/(2 * sinh(log(2) / 2 * bw)) + x = exp(0.5*log(2) * bw); + x/(sqr(x) - 1); +); + +function zdf_qtobw(q) + local(x) +( + // bw = 2 * asinh(1/(2 * q)) / log(2) + x = 0.5 / q; + 2/log(2) * log(x + sqrt(sqr(x) + 1)); +); + +function _zdf_seth() + instance(g, r2, h) +( + h = 1/((r2 + g)*g + 1); +); + +// Low-pass + +function zdf_lp(freq, q) + // global(srate) + instance(g, r2, cl, cb, ch) +( + g = tan($pi * min(freq / srate, 0.49)); + + r2 = 1/q; + cl = 1; + cb = 0; + ch = 0; + + this._zdf_seth(); +); + +// High-pass + +function zdf_hp(freq, q) + // global(srate) + instance(g, r2, cl, cb, ch) +( + g = tan($pi * min(freq / srate, 0.49)); + + r2 = 1/q; + cl = 0; + cb = 0; + ch = 1; + + this._zdf_seth(); +); + +// Band-pass (constant skirt gain, peak gain = Q) + +function zdf_bp(freq, q) + // global(srate) + instance(g, r2, cl, cb, ch) +( + g = tan($pi * min(freq / srate, 0.49)); + + r2 = 1/q; + cl = 0; + cb = 1; + ch = 0; + + this._zdf_seth(); +); + +// Band-pass (constant 0 dB peak gain) + +function zdf_bp2(freq, q) + // global(srate) + instance(g, r2, cl, cb, ch) +( + g = tan($pi * min(freq / srate, 0.49)); + + cl = 0; + cb = r2 = 1/q; + ch = 0; + + this._zdf_seth(); +); + +// Band-stop + +function zdf_bs(freq, q) + // global(srate) + instance(g, r2, cl, cb, ch) +( + g = tan($pi * min(freq / srate, 0.49)); + + r2 = 1/q; + cl = 1; + cb = 0; + ch = 1; + + this._zdf_seth(); +); + +// All-pass + +function zdf_ap(freq, q) + // global(srate) + instance(g, r2, cl, cb, ch) +( + g = tan($pi * min(freq / srate, 0.49)); + + r2 = 1/q; + cl = 1; + cb = -r2; + ch = 1; + + this._zdf_seth(); +); + +// Peaking EQ + +function zdf_eq(freq, q, gain) + // global(srate) + instance(g, r2, cl, cb, ch) +( + g = tan($pi * min(freq / srate, 0.49)); + + r2 = 1/(q * sqrt(gain)); + cl = 1; + cb = r2 * gain; + ch = 1; + + this._zdf_seth(); +); + +// Low-shelf + +function zdf_ls(freq, q, gain) + // global(srate) + instance(g, r2, cl, cb, ch) + local(a) +( + a = sqrt(gain); + g = tan($pi * min(freq / srate, 0.49)) / sqrt(a); + + r2 = 1/q; + cl = gain; + cb = r2 * a; + ch = 1; + + this._zdf_seth(); +); + +// High-shelf + +function zdf_hs(freq, q, gain) + // global(srate) + instance(g, r2, cl, cb, ch) + local(a) +( + a = sqrt(gain); + g = tan($pi * min(freq / srate, 0.49)) * sqrt(a); + + r2 = 1/q; + cl = 1; + cb = r2 * a; + ch = gain; + + this._zdf_seth(); +); + +function zdf_gain(gain) + instance(cl, cb, ch) +( + cl *= gain; + cb *= gain; + ch *= gain; +); + +function zdf_svf(sample) + instance(s1, s2, g, r2, h, cl, cb, ch) + local(yl, yb, yh) +( + // High-pass + yh = (sample - (r2 + g) * s1 - s2) * h; + + // Band-pass + yb = g*yh + s1; + s1 = g*yh + yb; + + // Zero denormals + abs(s1) < 0.00000000000000006 ? s1 = 0; + + // Low-pass + yl = g*yb + s2; + s2 = g*yb + yl; + + abs(s2) < 0.00000000000000006 ? s2 = 0; + + cl*yl + cb*yb + ch*yh; +); + +function zdf_svf_multi(sample) + instance(s1, s2, g, r2, h, cl, cb, ch, yl, yb, yh) +( + yh = (sample - (r2 + g) * s1 - s2) * h; + yb = g*yh + s1; + s1 = g*yh + yb; + abs(s1) < 0.00000000000000006 ? s1 = 0; + yl = g*yb + s2; + s2 = g*yb + yl; + abs(s2) < 0.00000000000000006 ? s2 = 0; + cl*yl + cb*yb + ch*yh; +); + +// Optimized versions of zdf_svf() returning only 1 of the 3 outputs. + +function zdf_setf(freq, q) + // global(srate) + instance(g, r2) +( + g = tan($pi * min(freq / srate, 0.49)); + r2 = 1/q; + this._zdf_seth(); +); + +function zdf_svf_lp(sample) + instance(s1, s2, g, r2, h) + local(yl, yb, yh) +( + yh = (sample - (r2 + g) * s1 - s2) * h; + yb = g*yh + s1; s1 = g*yh + yb; + abs(s1) < 0.00000000000000006 ? s1 = 0; + yl = g*yb + s2; s2 = g*yb + yl; + abs(s2) < 0.00000000000000006 ? s2 = 0; + yl; +); + +function zdf_svf_hp(sample) + instance(s1, s2, g, r2, h) + local(yl, yb, yh) +( + yh = (sample - (r2 + g) * s1 - s2) * h; + yb = g*yh + s1; s1 = g*yh + yb; + abs(s1) < 0.00000000000000006 ? s1 = 0; + yl = g*yb + s2; s2 = g*yb + yl; + abs(s2) < 0.00000000000000006 ? s2 = 0; + yh; +); + +function zdf_svf_bp(sample) + instance(s1, s2, g, r2, h) + local(yl, yb, yh) +( + yh = (sample - (r2 + g) * s1 - s2) * h; + yb = g*yh + s1; s1 = g*yh + yb; + abs(s1) < 0.00000000000000006 ? s1 = 0; + yl = g*yb + s2; s2 = g*yb + yl; + abs(s2) < 0.00000000000000006 ? s2 = 0; + yb; +); + +// Reset SVF state. + +function zdf_reset(input) + instance(s1, s2) +( + s1 = 0; + s2 = input; +); + +// Legacy + +// function zdf_bp2(freq, bw) ( this.zdf_bp(freq, zdf_bwtoq(bw)) ); +// function zdf_ap(freq, bw) ( this.zdf_ap(freq, zdf_bwtoq(bw)) ); +function zdf_notch(freq, bw) ( this.zdf_bs(freq, zdf_bwtoq(bw)) ); +function zdf_peak(freq, gain, bw) local(fc) ( fc = $pi * min(freq / srate, 0.49); this.zdf_eq(freq, zdf_bwtoq(bw) * fc / tan(fc), gain); ); +function zdf_low_shelf(freq, gain, bw) ( this.zdf_ls(freq, zdf_bwtoq(bw), gain) ); +function zdf_high_shelf(freq, gain, bw) ( this.zdf_hs(freq, zdf_bwtoq(bw), gain) ); +function zdf_mute() instance(g, r2, cl, cb, ch) ( g = tan(0.49*$pi); r2 = 1; cl = cb = ch = 0; this._zdf_seth(); ); +function zdf_bypass() instance(g, r2, cl, cb, ch) ( g = tan(0.49*$pi); r2 = cl = cb = ch = 1; this._zdf_seth(); ); +function zdf_bypass(freq, q) ( this.zdf_bypass() );