From f6939cd112c17d50d098bfd4990ab5cc11c399fa Mon Sep 17 00:00:00 2001 From: Krzysztof Foltman Date: Sat, 25 Apr 2009 23:41:19 +0100 Subject: [PATCH] + Monosynth: initial implementation of mod matrix (standalone only, no persistence, few sources/destinations, inefficient) --- gui/gui-monosynth.xml | 3 + src/calf/giface.h | 7 +- src/calf/gui.h | 2 +- src/calf/jackhost.h | 3 + src/calf/metadata.h | 4 +- src/calf/modules_synths.h | 72 +++++++++----------- src/gui.cpp | 25 +++++-- src/monosynth.cpp | 164 ++++++++++++++++++++++++++++++++++++++++++---- 8 files changed, 215 insertions(+), 65 deletions(-) diff --git a/gui/gui-monosynth.xml b/gui/gui-monosynth.xml index c484732..7226e64 100644 --- a/gui/gui-monosynth.xml +++ b/gui/gui-monosynth.xml @@ -214,5 +214,8 @@ + + + diff --git a/src/calf/giface.h b/src/calf/giface.h index eef4a89..320b04b 100644 --- a/src/calf/giface.h +++ b/src/calf/giface.h @@ -232,10 +232,10 @@ struct table_edit_iface virtual uint32_t get_table_rows(int param) = 0; /// retrieve data item from the plugin - virtual std::string get_cell(int param, int column) { return calf_utils::i2s(param)+":"+calf_utils::i2s(column); } + virtual std::string get_cell(int param, int row, int column) { return calf_utils::i2s(row)+":"+calf_utils::i2s(column); } /// set data item to the plugin - virtual void set_cell(int param, int column, const std::string &src, std::string &error) { error.clear(); } + virtual void set_cell(int param, int row, int column, const std::string &src, std::string &error) { error.clear(); } /// return a line graph interface for a specific parameter/column (unused for now) virtual line_graph_iface *get_graph_iface(int param, int column) { return NULL; } @@ -402,6 +402,9 @@ public: /// Handle MIDI Pitch Bend /// @param value pitch bend value (-8192 to 8191, defined as in MIDI ie. 8191 = 200 ct by default) inline void pitch_bend(int value) {} + /// Handle MIDI Channel Pressure + /// @param value channel pressure (0 to 127) + inline void channel_pressure(int value) {} /// Called when params are changed (before processing) inline void params_changed() {} /// LADSPA-esque activate function, except it is called after ports are connected, not before diff --git a/src/calf/gui.h b/src/calf/gui.h index 06d2860..de74547 100644 --- a/src/calf/gui.h +++ b/src/calf/gui.h @@ -313,7 +313,7 @@ struct listview_param_control: public param_control, public send_configure_iface virtual void get() {} virtual void set() {} virtual void send_configure(const char *key, const char *value); - void update_store(const std::string &data); + void update_store(); static void on_edited(GtkCellRenderer *renderer, gchar *path, gchar *new_text, listview_param_control *pThis); static void on_editing_canceled(GtkCellRenderer *renderer, listview_param_control *pThis); }; diff --git a/src/calf/jackhost.h b/src/calf/jackhost.h index 3223eb8..38b7b02 100644 --- a/src/calf/jackhost.h +++ b/src/calf/jackhost.h @@ -248,6 +248,9 @@ public: case 12: Module::program_change(buffer[1]); break; + case 13: + Module::channel_pressure(buffer[1]); + break; case 14: value = buffer[1] + 128 * buffer[2] - 8192; Module::pitch_bend(value); diff --git a/src/calf/metadata.h b/src/calf/metadata.h index a3ccb32..5b2b22b 100644 --- a/src/calf/metadata.h +++ b/src/calf/metadata.h @@ -114,13 +114,11 @@ struct monosynth_metadata: public plugin_metadata }; enum { moddest_none, - moddest_amplitude, + moddest_attenuation, moddest_cutoff, moddest_resonance, moddest_o1detune, moddest_o2detune, - moddest_o1pitch, - moddest_o2pitch, moddest_o1pw, moddest_o2pw, moddest_count, diff --git a/src/calf/modules_synths.h b/src/calf/modules_synths.h index f1ec9ee..bb054e4 100644 --- a/src/calf/modules_synths.h +++ b/src/calf/modules_synths.h @@ -35,11 +35,19 @@ namespace calf_plugins { #define MONOSYNTH_WAVE_BITS 12 +struct modulation_entry +{ + int src1, src2; + float amount; + int dest; +}; + /// Monosynth-in-making. Parameters may change at any point, so don't make songs with it! /// It lacks inertia for parameters, even for those that really need it. class monosynth_audio_module: public audio_module, public line_graph_iface, public table_edit_iface { public: + enum { mod_matrix_slots = 10 }; float *ins[in_count]; float *outs[out_count]; float *params[param_count]; @@ -76,8 +84,18 @@ public: dsp::adsr envelope; dsp::keystack stack; dsp::gain_smoothing master; + /// Smoothed cutoff value dsp::inertia inertia_cutoff; + /// Smoothed pitch bend value dsp::inertia inertia_pitchbend; + /// Smoothed channel pressure value + dsp::inertia inertia_pressure; + /// Rows of the modulation matrix + modulation_entry mod_matrix[mod_matrix_slots]; + /// Currently used velocity + float velocity; + /// Current calculated mod matrix outputs + float moddest[moddest_count]; monosynth_audio_module(); static void precalculate_waves(progress_report_iface *reporter); @@ -88,6 +106,8 @@ public: void note_on(int note, int vel); /// Handle MIDI Note Off message void note_off(int note, int vel); + /// Handle MIDI Channel Pressure + void channel_pressure(int value); /// Handle pitch bend message. inline void pitch_bend(int value) { @@ -99,8 +119,13 @@ public: float detune_scaled = (detune - 1); // * log(freq / 440); if (*params[par_scaledetune] > 0) detune_scaled *= pow(20.0 / freq, *params[par_scaledetune]); - osc1.set_freq(freq * (1 - detune_scaled) * inertia_pitchbend.get_last() * lfo_bend, srate); - osc2.set_freq(freq * (1 + detune_scaled) * inertia_pitchbend.get_last() * lfo_bend * xpose, srate); + float p1 = 1, p2 = 1; + if (moddest[moddest_o1detune] != 0) + p1 = pow(2.0, moddest[moddest_o1detune] * (1.0 / 1200.0)); + if (moddest[moddest_o2detune] != 0) + p2 = pow(2.0, moddest[moddest_o2detune] * (1.0 / 1200.0)); + osc1.set_freq(freq * (1 - detune_scaled) * p1 * inertia_pitchbend.get_last() * lfo_bend, srate); + osc2.set_freq(freq * (1 + detune_scaled) * p2 * inertia_pitchbend.get_last() * lfo_bend * xpose, srate); } /// Handle control change messages. void control_change(int controller, int value); @@ -152,48 +177,15 @@ public: bool is_noisy(int param_no) { return param_no != par_cutoff; } /// Calculate control signals and produce step_size samples of output. void calculate_step(); + /// Process modulation matrix + void calculate_modmatrix(float *modsrc); /// Main processing function - uint32_t process(uint32_t offset, uint32_t nsamples, uint32_t inputs_mask, uint32_t outputs_mask) { - if (!running && queue_note_on == -1) { - for (uint32_t i = 0; i < nsamples / step_size; i++) - envelope.advance(); - return 0; - } - uint32_t op = offset; - uint32_t op_end = offset + nsamples; - while(op < op_end) { - if (output_pos == 0) { - if (running || queue_note_on != -1) - calculate_step(); - else { - envelope.advance(); - dsp::zero(buffer, step_size); - } - } - if(op < op_end) { - uint32_t ip = output_pos; - uint32_t len = std::min(step_size - output_pos, op_end - op); - if (is_stereo_filter()) - for(uint32_t i = 0 ; i < len; i++) { - float vol = master.get(); - outs[0][op + i] = buffer[ip + i] * vol, - outs[1][op + i] = buffer2[ip + i] * vol; - } - else - for(uint32_t i = 0 ; i < len; i++) - outs[0][op + i] = outs[1][op + i] = buffer[ip + i] * master.get(); - op += len; - output_pos += len; - if (output_pos == step_size) - output_pos = 0; - } - } - - return 3; - } + uint32_t process(uint32_t offset, uint32_t nsamples, uint32_t inputs_mask, uint32_t outputs_mask); virtual const table_column_info *get_table_columns(int param); virtual uint32_t get_table_rows(int param); + virtual std::string get_cell(int param, int row, int column); + virtual void set_cell(int param, int row, int column, const std::string &src, std::string &error); }; struct organ_audio_module: public audio_module, public dsp::drawbar_organ, public line_graph_iface diff --git a/src/gui.cpp b/src/gui.cpp index f713e38..81a7869 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -710,7 +710,7 @@ GtkWidget *listview_param_control::create(plugin_gui *_gui, int _param_no) for (int i = 0; i < cols; i++) p[i] = G_TYPE_STRING; lstore = gtk_list_store_newv(cols, p); - update_store(""); + update_store(); widget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(lstore)); delete []p; tree = GTK_TREE_VIEW(widget); @@ -743,7 +743,7 @@ GtkWidget *listview_param_control::create(plugin_gui *_gui, int _param_no) return widget; } -void listview_param_control::update_store(const std::string &data) +void listview_param_control::update_store() { gtk_list_store_clear(lstore); uint32_t rows = teif->get_table_rows(param_no); @@ -753,7 +753,7 @@ void listview_param_control::update_store(const std::string &data) gtk_list_store_insert(lstore, &iter, i); for (int j = 0; j < cols; j++) { - gtk_list_store_set(lstore, &iter, j, teif->get_cell(i, j).c_str(), -1); + gtk_list_store_set(lstore, &iter, j, teif->get_cell(param_no, i, j).c_str(), -1); } positions.push_back(iter); } @@ -763,15 +763,28 @@ void listview_param_control::send_configure(const char *key, const char *value) { if (attribs["key"] == key) { - update_store(value); + update_store(); } } void listview_param_control::on_edited(GtkCellRenderer *renderer, gchar *path, gchar *new_text, listview_param_control *pThis) { const table_column_info *tci = pThis->teif->get_table_columns(pThis->param_no); - gtk_list_store_set(pThis->lstore, &pThis->positions[atoi(path)], ((table_column_info *)g_object_get_data(G_OBJECT(renderer), "column")) - tci, new_text, -1); - gtk_widget_grab_focus(pThis->widget); + int column = ((table_column_info *)g_object_get_data(G_OBJECT(renderer), "column")) - tci; + string error; + pThis->teif->set_cell(pThis->param_no, atoi(path), column, new_text, error); + if (error.empty()) { + pThis->update_store(); + gtk_widget_grab_focus(pThis->widget); + } + else + { + GtkWidget *dialog = gtk_message_dialog_new(pThis->gui->window->toplevel, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "%s", error.c_str()); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + gtk_widget_grab_focus(pThis->widget); + } } void listview_param_control::on_editing_canceled(GtkCellRenderer *renderer, listview_param_control *pThis) diff --git a/src/monosynth.cpp b/src/monosynth.cpp index 9e66851..e4c858e 100644 --- a/src/monosynth.cpp +++ b/src/monosynth.cpp @@ -35,9 +35,17 @@ using namespace std; float silence[4097]; monosynth_audio_module::monosynth_audio_module() -: inertia_cutoff(exponential_ramp(1)) -, inertia_pitchbend(exponential_ramp(1)) +: inertia_cutoff(1) +, inertia_pitchbend(1) +, inertia_pressure(64) { + for (int i = 0; i < mod_matrix_slots; i++) + { + mod_matrix[i].src1 = modsrc_none; + mod_matrix[i].src2 = modsrc_none; + mod_matrix[i].amount = 0.f; + mod_matrix[i].dest = moddest_none; + } } void monosynth_audio_module::activate() { @@ -50,6 +58,7 @@ void monosynth_audio_module::activate() { modwheel_value = 0.f; modwheel_value_int = 0; inertia_cutoff.set_now(*params[par_cutoff]); + inertia_pressure.set_now(0); filter.reset(); filter2.reset(); stack.clear(); @@ -233,8 +242,8 @@ void monosynth_audio_module::calculate_buffer_oscs(float lfo) int flag2 = (wave2 == wave_sqr); int32_t shift1 = last_pwshift1; int32_t shift2 = last_pwshift2; - int32_t shift_target1 = (int32_t)(0x78000000 * dsp::clip11(*params[par_pw1] + lfo * *params[par_lfopw])); - int32_t shift_target2 = (int32_t)(0x78000000 * dsp::clip11(*params[par_pw2] + lfo * *params[par_lfopw])); + int32_t shift_target1 = (int32_t)(0x78000000 * dsp::clip11(*params[par_pw1] + lfo * *params[par_lfopw] + moddest[moddest_o1pw])); + int32_t shift_target2 = (int32_t)(0x78000000 * dsp::clip11(*params[par_pw2] + lfo * *params[par_lfopw] + moddest[moddest_o2pw])); int32_t shift_delta1 = ((shift_target1 >> 1) - (last_pwshift1 >> 1)) >> (step_shift - 1); int32_t shift_delta2 = ((shift_target2 >> 1) - (last_pwshift2 >> 1)) >> (step_shift - 1); last_lfov = lfo; @@ -313,6 +322,7 @@ void monosynth_audio_module::delayed_note_on() porta_time = 0.f; start_freq = freq; target_freq = freq = 440 * pow(2.0, (queue_note_on - 69) / 12.0); + velocity = queue_vel; ampctl = 1.0 + (queue_vel - 1.0) * *params[par_vel2amp]; fltctl = 1.0 + (queue_vel - 1.0) * *params[par_vel2filter]; set_frequency(); @@ -363,6 +373,8 @@ void monosynth_audio_module::delayed_note_on() } envelope.advance(); queue_note_on = -1; + float modsrc[modsrc_count] = { 1, velocity, inertia_pressure.get_last(), modwheel_value, 0, last_lfov}; + calculate_modmatrix(modsrc); } void monosynth_audio_module::set_sample_rate(uint32_t sr) { @@ -376,6 +388,18 @@ void monosynth_audio_module::set_sample_rate(uint32_t sr) { inertia_pitchbend.ramp.set_length(crate / 30); // 1/30s } +void monosynth_audio_module::calculate_modmatrix(float *modsrc) +{ + for (int i = 0; i < moddest_count; i++) + moddest[i] = 0; + for (int i = 0; i < mod_matrix_slots; i++) + { + modulation_entry &slot = mod_matrix[i]; + if (slot.dest) + moddest[slot.dest] += modsrc[slot.src1] * modsrc[slot.src2] * slot.amount; + } +} + void monosynth_audio_module::calculate_step() { if (queue_note_on != -1) @@ -413,15 +437,21 @@ void monosynth_audio_module::calculate_step() set_frequency(); envelope.advance(); float env = envelope.value; + + // mod matrix + // this should be optimized heavily; I think I'll do it when MIDI in Ardour 3 gets stable :> + float modsrc[modsrc_count] = { 1, velocity, inertia_pressure.get(), modwheel_value, env, lfov}; + calculate_modmatrix(modsrc); + inertia_cutoff.set_inertia(*params[par_cutoff]); - cutoff = inertia_cutoff.get() * pow(2.0f, (lfov * *params[par_lfofilter] + env * fltctl * *params[par_envmod]) * (1.f / 1200.f)); + cutoff = inertia_cutoff.get() * pow(2.0f, (lfov * *params[par_lfofilter] + env * fltctl * *params[par_envmod] + moddest[moddest_cutoff]) * (1.f / 1200.f)); if (*params[par_keyfollow] > 0.01f) cutoff *= pow(freq / 264.f, *params[par_keyfollow]); cutoff = dsp::clip(cutoff , 10.f, 18000.f); float resonance = *params[par_resonance]; float e2r = *params[par_envtores]; float e2a = *params[par_envtoamp]; - resonance = resonance * (1 - e2r) + (0.7 + (resonance - 0.7) * env * env) * e2r; + resonance = resonance * (1 - e2r) + (0.7 + (resonance - 0.7) * env * env) * e2r + moddest[moddest_resonance]; float cutoff2 = dsp::clip(cutoff * separation, 10.f, 18000.f); float newfgain = 0.f; if (filter_type != last_filter_type) @@ -476,6 +506,8 @@ void monosynth_audio_module::calculate_step() float aenv = env; if (*params[par_envtoamp] > 0.f) newfgain *= 1.0 - (1.0 - aenv) * e2a; + if (moddest[moddest_attenuation] != 0.f) + newfgain *= dsp::clip(1 - moddest[moddest_attenuation] * moddest[moddest_attenuation], 0.f, 1.f); fgain_delta = (newfgain - fgain) * (1.0 / step_size); calculate_buffer_oscs(lfov); switch(filter_type) @@ -541,6 +573,10 @@ void monosynth_audio_module::note_off(int note, int vel) } } +void monosynth_audio_module::channel_pressure(int value) +{ + inertia_pressure.set_inertia(value * (1.0 / 127.0)); +} void monosynth_audio_module::control_change(int controller, int value) { @@ -575,6 +611,45 @@ void monosynth_audio_module::deactivate() stack.clear(); } +uint32_t monosynth_audio_module::process(uint32_t offset, uint32_t nsamples, uint32_t inputs_mask, uint32_t outputs_mask) { + if (!running && queue_note_on == -1) { + for (uint32_t i = 0; i < nsamples / step_size; i++) + envelope.advance(); + return 0; + } + uint32_t op = offset; + uint32_t op_end = offset + nsamples; + while(op < op_end) { + if (output_pos == 0) { + if (running || queue_note_on != -1) + calculate_step(); + else { + envelope.advance(); + dsp::zero(buffer, step_size); + } + } + if(op < op_end) { + uint32_t ip = output_pos; + uint32_t len = std::min(step_size - output_pos, op_end - op); + if (is_stereo_filter()) + for(uint32_t i = 0 ; i < len; i++) { + float vol = master.get(); + outs[0][op + i] = buffer[ip + i] * vol, + outs[1][op + i] = buffer2[ip + i] * vol; + } + else + for(uint32_t i = 0 ; i < len; i++) + outs[0][op + i] = outs[1][op + i] = buffer[ip + i] * master.get(); + op += len; + output_pos += len; + if (output_pos == step_size) + output_pos = 0; + } + } + + return 3; +} + static const char *monosynth_mod_src_names[] = { "None", "Velocity", @@ -587,16 +662,11 @@ static const char *monosynth_mod_src_names[] = { static const char *monosynth_mod_dest_names[] = { "None", - "Amplitude", + "Attenuation", "Cutoff", "Resonance", - "OX: Detune", "O1: Detune", "O2: Detune", - "OX: Pitch", - "O1: Pitch", - "O2: Pitch", - "OX: PW", "O1: PW", "O2: PW", NULL @@ -617,5 +687,73 @@ const table_column_info *monosynth_audio_module::get_table_columns(int param) uint32_t monosynth_audio_module::get_table_rows(int param) { - return 2; + return mod_matrix_slots; } + +std::string monosynth_audio_module::get_cell(int param, int row, int column) +{ + assert(row >= 0 && row < mod_matrix_slots); + modulation_entry &slot = mod_matrix[row]; + switch(column) { + case 0: // source 1 + return monosynth_mod_src_names[slot.src1]; + case 1: // source 2 + return monosynth_mod_src_names[slot.src2]; + case 2: // amount + return calf_utils::f2s(slot.amount); + case 3: // destination + return monosynth_mod_dest_names[slot.dest]; + default: + assert(0); + return ""; + } +} + +void monosynth_audio_module::set_cell(int param, int row, int column, const std::string &src, std::string &error) +{ + assert(row >= 0 && row < mod_matrix_slots); + modulation_entry &slot = mod_matrix[row]; + switch(column) { + case 0: + case 1: + { + for (int i = 0; monosynth_mod_src_names[i]; i++) + { + if (src == monosynth_mod_src_names[i]) + { + if (column == 0) + slot.src1 = i; + else + slot.src2 = i; + error.clear(); + return; + } + } + error = "Invalid source name"; + return; + } + case 2: + { + stringstream ss(src); + ss >> slot.amount; + error.clear(); + return; + } + case 3: + { + for (int i = 0; monosynth_mod_dest_names[i]; i++) + { + if (src == monosynth_mod_dest_names[i]) + { + slot.dest = i; + error.clear(); + return; + } + } + error = "Invalid destination name"; + return; + } + + } +} + -- 2.11.4.GIT