From 32491f3b81541d4dd645ac9845fc5fa3bf5171df Mon Sep 17 00:00:00 2001 From: Joep Vanlier Date: Wed, 22 May 2024 02:15:38 +0200 Subject: [PATCH] protosynth: improve voice stealing --- .../saike_proto_synth_kp_midi.jsfx-inc | 47 ++++++-- .../saike_proto_synth_os_nl_filters.jsfx-inc | 107 ++++++++++++++++-- protosynth/saike_protosynth.jsfx | 25 ++-- 3 files changed, 152 insertions(+), 27 deletions(-) diff --git a/protosynth/protosynth_dependencies/saike_proto_synth_kp_midi.jsfx-inc b/protosynth/protosynth_dependencies/saike_proto_synth_kp_midi.jsfx-inc index d3772e9..aac300d 100644 --- a/protosynth/protosynth_dependencies/saike_proto_synth_kp_midi.jsfx-inc +++ b/protosynth/protosynth_dependencies/saike_proto_synth_kp_midi.jsfx-inc @@ -14,18 +14,20 @@ global(CUSTOM_SLIDER) function init_midi(freemem) instance(note_mem, - active_note_mem, active_note_vel, active_note_state, note_history, sustaining, sustain_history, num_sustain, + active_note_mem, active_note_vel, active_note_state, note_history, sustaining, sustain_history, num_sustain, note_release_mem, max_notes, notes_on, pitchbend, modwheel) global() ( max_notes = 12; freemem = (note_mem = freemem) + 32768; - freemem = (active_note_mem = freemem) + 4096; + freemem = (active_note_mem = freemem) + 2048; + freemem = (note_release_mem = freemem) + 2048; freemem = (active_note_vel = freemem) + 4096; freemem = (active_note_state = freemem) + 4096; freemem = (note_history = freemem) + 4096; freemem = (sustain_history = freemem) + 4096; memset(active_note_mem, 0, 128); + memset(note_release_mem, 0, 128); memset(sustain_history, 0, 128); memset(note_mem, 0, 32768); notes_on = 0; @@ -161,7 +163,7 @@ local(offset, msg1, msg2, msg3, note_on, note_off, mwCC, pb, lastpb, idx, found) function midi_clear_notes() local(i) -instance(notes_on, active_note_mem, active_note_state, active_note_vel, note_history, max_notes) +instance(notes_on, active_note_mem, active_note_state, active_note_vel, note_history, max_notes, note_release_mem, release_idx) global() ( // Terminate all notes @@ -174,6 +176,8 @@ global() active_note_state[i] = -1; //active_note_vel[i] = 0; note_history[i] = 0; + release_idx += 1; + note_release_mem[i] = release_idx; ); i += 1; ); @@ -183,7 +187,7 @@ global() // midi.notes_remain function midi_sample(respond_to_notes) instance(note_next, curSample, note_ptr, notes_remain, notes_on, note, velocity, - active_note_mem, active_note_vel, active_note_state, note_history, + active_note_mem, active_note_vel, active_note_state, note_history, note_release_mem, release_idx, cur_cc, cc_target, max_notes, modwheel, pitchbend, last_vel, last_slot) local(i, i2, found, current_item, change, not_released) global(MAX_STRING, voice_thievery, glide) @@ -223,11 +227,11 @@ global(MAX_STRING, voice_thievery, glide) found = 1337; i = last_slot + 1; - (i >= MAX_STRING) ? i = 0; + (i >= MAX_NOTES) ? i = 0; !voice_thievery ? ( // Try just cycling first. - while((found == 1337) && (i < MAX_STRING)) + while((found == 1337) && (i < MAX_NOTES)) ( (active_note_mem[i] == 0) ? found = i; i += 1; @@ -235,7 +239,7 @@ global(MAX_STRING, voice_thievery, glide) ); i = 0; - while((found == 1337) && (i < max_notes)) + while((found == 1337) && (i < MAX_NOTES)) ( (active_note_mem[i] == 0) ? found = i; i += 1; @@ -243,12 +247,29 @@ global(MAX_STRING, voice_thievery, glide) // Try cycling through released notes next (found > MAX_NOTES) ? ( - i = 0; - while((found == 1337) && (i < max_notes)) - ( - (active_note_mem[i] < 0) ? found = i; + i = 0; + i2 = 90071992547409; // 9007199254740992; + + // Find the oldest one to recycle + loop(MAX_NOTES, + (active_note_mem[i] < 0) ? ( + (note_release_mem[i] < i2) ? ( + i2 = note_release_mem[i]; + found = i; + ); + ); i += 1; ); + + // We are about to recycle an existing one, so we need to drop the old one from the note history + (found < MAX_NOTES) ? ( + i2 = found; + loop(max_notes, + note_history[i2] = note_history[i2 + 1]; + i2 += 1; + ); + notes_on -= 1; + ); ); (found <= MAX_NOTES) ? ( @@ -295,6 +316,10 @@ global(MAX_STRING, voice_thievery, glide) ); ); active_note_mem[i] = -abs(active_note_mem[i]); // Released + + release_idx += 1; + note_release_mem[i] = release_idx; + //active_note_vel[i] = 0; active_note_state[i] = -1; /* Release state */ change = 1; diff --git a/protosynth/protosynth_dependencies/saike_proto_synth_os_nl_filters.jsfx-inc b/protosynth/protosynth_dependencies/saike_proto_synth_os_nl_filters.jsfx-inc index ba83ec1..9c2e369 100644 --- a/protosynth/protosynth_dependencies/saike_proto_synth_os_nl_filters.jsfx-inc +++ b/protosynth/protosynth_dependencies/saike_proto_synth_os_nl_filters.jsfx-inc @@ -886,7 +886,7 @@ local( instance(res, iter, v0n, v1, v2, k, wc, g_v1, mo0, mo1, mo2, env_est) ( v0 = v0 + v0 + v0; - env_est = max(0.5, min(1.0, max(abs(v0 + v0), 0.9998 * env_est))); + env_est = max(0.5, min(1.0, max(abs(v0 + v0), 0.9998 * env_est))); // TODO: make this samplerate independent k_factor = 1.0 + preamp * (1.0 - k) * 0.2; k_dim = (k - k_factor) * env_est + (1 - env_est); @@ -925,6 +925,94 @@ instance(res, iter, v0n, v1, v2, k, wc, g_v1, mo0, mo1, mo2, env_est) mo0 * v0 + mo1 * v1 + mo2 * v2 ); +function c_svf3(x) +local() +global() +instance() +( + x / (1 + abs(0.1 * x)) +); + +function dc_svf3(x) +local(ax) +global() +instance() +( + ax = 1.0 / (0.1 * abs(x) + 1); + ax * ax +); + +function f_svf3(x) +local() +global() +instance() +( + x / (1 + abs(0.1 * x)) +); + +function df_svf3(x) +local(ax) +global() +instance() +( + ax = 1.0 / (0.1 * abs(x) + 1); + ax * ax +); + +function _eval_svf3(v0) +global(epsilon, maxiter_svf, preamp) +local( + f1, f2, fh1, fh2, + f1_const, f2_const, /* Constant part of the implicit equation */ + norm, + a, b, c, d, /* Jacobian elements */ + dg_v1, cterm, + k_factor, k_dim, qsq, +) +instance(res, iter, v0n, v1, v2, k, wc, g_v1, mo0, mo1, mo2, env_est, wcm, v_lp) +( + v0 = v0 + v0 + v0; + env_est = max(0.5, min(1.5, max(abs(v0 + v0), 0.9998 * env_est))); // TODO: make this samplerate independent + k_factor = 1.0 + preamp * (1.0 - k) * 0.2; + k_dim = (k - k_factor) * env_est + (1 - env_est); + + v_lp = v_lp * 0.999 + 0.001 * (v2 - 0.01 * rand()); // TODO: make this samplerate independent + wcm = min(wc * (0.9 + 0.3 * abs(v_lp)), 0.98); + + /* Calculate fixed stuff from previous iteration */ + f1_const = wcm * c_svf3(v0n - v2 - 2 * (v1 + f_svf3(g_v1 * k_dim))) + v1; + f2_const = wcm * g_v1 + v2; + + v0n = v0; + + iter = 0; + while( + iter += 1; + + g_v1 = v1 / (1.0 + abs(v1)); + dg_v1 = dg_svf2(v1); + + // Residual + cterm = v0 - v2 - 2 * (v1 + f_svf3(g_v1 * k_dim)); + f1 = wcm * c_svf3(cterm) - v1 + f1_const; + f2 = wcm * g_v1 - v2 + f2_const; + + a = -2 * wcm * ((k - 1) * dg_v1 * df_svf3(k_dim * g_v1) + 1.0) * dc_svf3(cterm) - 1.0; + b = - wcm; + c = wcm * dg_v1; + d = -1; + + res = abs(f1) + abs(f2); + + norm = 1.0 / ( a * d - b * c ); + v1 = v1 - ( d*f1 - b*f2 ) * norm; + v2 = v2 - ( a*f2 - c*f1 ) * norm; + + (res > epsilon) && (iter < maxiter_svf); + ); + + mo0 * v0 + mo1 * v1 + mo2 * v2 +); function run_shriek(x) instance(last_x) @@ -933,17 +1021,20 @@ global() ( this.elliptic_2x(this._eval_svf2(last_x)); y = this.elliptic_2x(this._eval_svf2(0.5 * (last_x + x))); - - /*this.elliptic_4x(this._eval_svf2(last_x))); - this.elliptic_4x(this._eval_svf2(0.75 * last_x + 0.25 * x)); - this.elliptic_4x(this._eval_svf2(0.5 * (last_x + x))); - y = this.elliptic_4x(this._eval_svf2(0.25 * last_x + 0.75 * x));*/ - - last_x = x; y; ); +function run_mj(x) +instance(last_x) +local(y) +global() +( + this.elliptic_2x(this._eval_svf3(last_x)); + y = this.elliptic_2x(this._eval_svf3(0.5 * (last_x + x))); + last_x = x; + y; +); function init_linear(freq, res, morph) global(srate) diff --git a/protosynth/saike_protosynth.jsfx b/protosynth/saike_protosynth.jsfx index 6d9fecf..f5999fe 100644 --- a/protosynth/saike_protosynth.jsfx +++ b/protosynth/saike_protosynth.jsfx @@ -1,9 +1,9 @@ desc:Saike Protosynth options:maxmem=32000000 tags: instrument, synth, generator, synthesizer -version: 0.22 +version: 0.23 author: Joep Vanlier -changelog: Add a few more presets. +changelog: Add extra filter type. Improve note reassignment (reuse oldest released note first). license: MIT provides: protosynth_dependencies/* @@ -83,14 +83,14 @@ slider151:f2_morph=0<0,1,0.000001>-F2 Morph slider152:f3_cutoff=1<0,1,0.000001>-F3 Filter Cutoff slider153:f3_reso=0<0,1,0.000001>-F3 Filter Resonance slider154:f3_morph=0<0,1,0.000001>-F3 Filter Morph -slider155:f3_type=0<0,1,5{LIN,MS,L2,L4,SHRK,PL>-F3 Type +slider155:f3_type=0<0,1,5{LIN,MS,L2,L4,SHRK,PL,SHRK2>-F3 Type slider156:f3_drive=0<-6,48,0.00001>-F3 Drive slider157:keyfollow=0<0,1,0.00001>-Key follow slider158:f4_cutoff=1<0,1,0.000001>-Output Filter Cutoff slider159:f4_reso=0<0,1,0.000001>-Output Filter Resonance slider160:f4_morph=0<0,1,0.000001>-Output Filter Morph -slider161:f4_type=0<0,5,1{LIN,MS,L2,L4,SHRK,PL>-Output Filter Type +slider161:f4_type=0<0,5,1{LIN,MS,L2,L4,SHRK,PL,SHRK2>-Output Filter Type slider162:f4_drive=0<-6,24,0.00001>-Output Drive slider163:f4_keyfollow=0<0,1,0.00001>-Output Key follow @@ -260,11 +260,11 @@ instance() ); ); -filter_type_slider1.init_slider_ui(-1, 0, 5, 0); +filter_type_slider1.init_slider_ui(-1, 0, 6, 0); filter_gain_slider3.init_slider_ui(156, -24, 24, 0); filter_keyfollow1.init_slider_ui(157, 0, 1, 0); -filter_type_slider2.init_slider_ui(-1, 0, 5, 0); +filter_type_slider2.init_slider_ui(-1, 0, 6, 0); filter_gain_slider4.init_slider_ui(162, -24, 24, 0); filter_keyfollow2.init_slider_ui(163, 0, 1, 0); @@ -482,6 +482,7 @@ function filt_type(type) : (type == 3) ? "L4" : (type == 4) ? "Shrk" : (type == 5) ? "PL" + : (type == 6) ? "Sh2" ); // Modulator handling @@ -743,6 +744,8 @@ instance( this.filter3.shriek.init_shriek(f_cutoff, f_reso, f_morph); ) : (current_f3_type == 5) ? ( this.filter3.pl4.init_pl4(f_cutoff, f_reso, f_morph); + ): (current_f3_type == 6) ? ( + this.filter3.shriek.init_shriek(f_cutoff, f_reso, f_morph); ); ); ); @@ -917,6 +920,8 @@ instance( this.filter3.shriek.run_shriek(o1 * current_f3_gain) / current_f3_gain; ) : (current_f3_type == 5) ? ( this.filter3.pl4.run_pl4(o1 * current_f3_gain) / current_f3_gain; + ) : (current_f3_type == 6) ? ( + this.filter3.shriek.run_mj(o1 * current_f3_gain) / current_f3_gain; ) ) : o1; @@ -1502,6 +1507,8 @@ voice_update ? ( this.filter4.shriek.init_shriek(f_cutoff, f_reso, f_morph); ) : (current_f4_type == 5) ? ( this.filter4.pl4.init_pl4(f_cutoff, f_reso, f_morph); + ) : (current_f4_type == 6) ? ( + this.filter4.shriek.init_shriek(f_cutoff, f_reso, f_morph); ); ); ); @@ -1538,6 +1545,8 @@ spl0 = filter4_enabled ? ( this.filter4.shriek.run_shriek(spl0 * current_f4_gain) / current_f4_gain; ) : (current_f4_type == 5) ? ( this.filter4.pl4.run_pl4(spl0 * current_f4_gain) / current_f4_gain; + ) : (current_f4_type == 6) ? ( + this.filter4.shriek.run_mj(spl0 * current_f4_gain) / current_f4_gain; ) ) : spl0; @@ -1896,7 +1905,7 @@ cy += 5; printf(" );"); printf(");"); - printf("mod_freq_slider%d.text_slider_ui(%d, cx, cy, 5 * toggle_size + 4, 8, sprintf(9, \"%%\dHz\", mod_freq_slider%d.display_value), \"Frequency\", \"Frequency\"); cx += 5 * toggle_size + 4 + 2;", i + 1, uuid(), i + 1); + printf("mod_freq_slider%d.text_slider_ui(%d, cx, cy, 5 * toggle_size + 4, 8, (mod_freq_slider%d.display_value >= 10) ? sprintf(9, \"%%\dHz\", mod_freq_slider%d.display_value) : sprintf(9, \"%%.1\fHz\", mod_freq_slider%d.display_value), \"Frequency\", \"Frequency\"); cx += 5 * toggle_size + 4 + 2;", i + 1, uuid(), i + 1, i + 1, i + 1); printf("mod_freq_slider%d.modulator_field(%d, cx, cy, 8, 1, MODULATOR_STRING, \"Freq mod\", 0, N_SELECTABLE_MODULATORS, 1);\n cx += 10;", i + 1, uuid()); printf("mod_freq_slider%d.check_text_input_defaults();", i + 1); @@ -2220,7 +2229,7 @@ cy = 208 - 10; printf("(filter_type_slider%d.over && (mouse_cap == 2) && (last_cap == 0)) ? (\n", j); printf("gfx_x = mouse_x;"); printf("gfx_y = mouse_y;"); - printf("ix = gfx_showmenu(\"Linear|Nonlinear 2-pole MS-20|Nonlinear 2-pole ladder|Nonlinear 4-pole ladder|Nonlinear shriek 2-pole|Nonlinear pillow 4-pole\");"); + printf("ix = gfx_showmenu(\"Linear|Nonlinear 2-pole MS-20|Nonlinear 2-pole ladder|Nonlinear 4-pole ladder|Nonlinear shriek 2-pole|Nonlinear pillow 4-pole|Shriek 2\");"); printf(" (ix > 0) ? ("); printf(" filter_type_slider%d.current_value = ix - 1;", j); printf(" );");