Skip to content

Commit

Permalink
Open Sound Control: better reporting of oscillator changes (surge-syn…
Browse files Browse the repository at this point in the history
…thesizer#7760)

Improve the OSC echoing when an oscillator type is changed (echo all oscillator parameters on change). Fix error in OSC spec.

---------

Co-authored-by: Paul Walker <paul@pwjw.com>
  • Loading branch information
pkstone and baconpaul authored Aug 15, 2024
1 parent 698cbba commit 51b9dc7
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 62 deletions.
50 changes: 25 additions & 25 deletions resources/surge-shared/oscspecification.html
Original file line number Diff line number Diff line change
Expand Up @@ -888,65 +888,65 @@ <h3>Replace '&lt;s&gt;' with 'a' or 'b' (scene) and &lt;n&gt; with '1' through '
<th>Appropriate Values</th>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/keytrack</td>
<td> Osc &lt;n&gt; Keytrack</td>
<td>boolean (0 or 1)</td>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/type</td>
<td> Osc &lt;n&gt; Type</td>
<td>integer (0 to 11)</td>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/octave</td>
<td> Osc &lt;n&gt; Octave</td>
<td>integer (-3 to 3)</td>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/keytrack</td>
<td> Osc &lt;n&gt; Keytrack</td>
<td>boolean (0 or 1)</td>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/pitch</td>
<td> Osc &lt;n&gt; Pitch</td>
<td>float (0.0 to 1.0)</td>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/retrigger</td>
<td> Osc &lt;n&gt; Retrigger</td>
<td>boolean (0 or 1)</td>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/param1</td>
<td> Osc &lt;n&gt; Morph</td>
<td> Osc &lt;n&gt; Param 1</td>
<td>(contextual)</td>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/param2</td>
<td> Osc &lt;n&gt; Skew Vertical</td>
<td> Osc &lt;n&gt; Param 2</td>
<td>(contextual)</td>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/param3</td>
<td> Osc &lt;n&gt; Saturate</td>
<td> Osc &lt;n&gt; Param 3</td>
<td>(contextual)</td>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/param4</td>
<td> Osc &lt;n&gt; Formant</td>
<td> Osc &lt;n&gt; Param 4</td>
<td>(contextual)</td>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/param5</td>
<td> Osc &lt;n&gt; Skew Horizontal</td>
<td> Osc &lt;n&gt; Param 5</td>
<td>(contextual)</td>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/param6</td>
<td> Osc &lt;n&gt; Unison Detune</td>
<td> Osc &lt;n&gt; Param 6</td>
<td>(contextual)</td>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/param7</td>
<td> Osc &lt;n&gt; Unison Voices</td>
<td> Osc &lt;n&gt; Param 7</td>
<td>(contextual)</td>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/pitch</td>
<td> Osc &lt;n&gt; Pitch</td>
<td>float (0.0 to 1.0)</td>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/retrigger</td>
<td> Osc &lt;n&gt; Retrigger</td>
<td>boolean (0 or 1)</td>
</tr>
<tr>
<td>/param/&lt;s&gt;/osc/&lt;n&gt;/type</td>
<td> Osc &lt;n&gt; Type</td>
<td>integer (0 to 11)</td>
</tr>
</table>
</div>
<div class="tablewrap fr cr">
Expand Down
59 changes: 41 additions & 18 deletions src/common/SurgeSynthesizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2404,7 +2404,7 @@ void SurgeSynthesizer::channelController(char channel, int cc, int value)
// Notify audio thread param change listeners (OSC, e.g.)
// (which run on juce messenger thread)
for (const auto &it : audioThreadParamListeners)
(it.second)(storage.getPatch().param_ptr[i]->oscName, fval);
(it.second)(storage.getPatch().param_ptr[i]->oscName, fval, "");

int j = 0;
while (j < 7)
Expand Down Expand Up @@ -3196,82 +3196,105 @@ bool SurgeSynthesizer::loadOscalgos()
for (int i = 0; i < n_oscs; i++)
{
localResendOscParams[s][i] = false;
if (storage.getPatch().scene[s].osc[i].queue_type > -1)
auto &osc_st = storage.getPatch().scene[s].osc[i];
if (osc_st.queue_type > -1)
{
algosChanged = true;
// clear assigned modulation if we change osc type, see issue #2224
if (storage.getPatch().scene[s].osc[i].queue_type !=
storage.getPatch().scene[s].osc[i].type.val.i)
// clear assigned modulation, and echo to OSC if we change osc type, see issue #2224
if (osc_st.queue_type != osc_st.type.val.i)
{
clear_osc_modulation(s, i);
}

storage.getPatch().scene[s].osc[i].type.val.i =
storage.getPatch().scene[s].osc[i].queue_type;
storage.getPatch().update_controls(false, &storage.getPatch().scene[s].osc[i]);
storage.getPatch().scene[s].osc[i].queue_type = -1;
// Notify audio thread param change listeners (OSC, e.g.)
// (which run on juce messenger thread)
std::stringstream sb;
sb << "/param/" << (char)('a' + s) << "/osc/" << i + 1 << "/type";
auto new_type = osc_st.queue_type;
for (const auto &it : audioThreadParamListeners)
(it.second)(sb.str(), new_type, osc_type_names[new_type]);

osc_st.type.val.i = osc_st.queue_type;
storage.getPatch().update_controls(false, &osc_st);
osc_st.queue_type = -1;
switch_toggled_queued = true;
refresh_editor = true;
localResendOscParams[s][i] = true;
}

TiXmlElement *e = (TiXmlElement *)storage.getPatch().scene[s].osc[i].queue_xmldata;
TiXmlElement *e = (TiXmlElement *)osc_st.queue_xmldata;
if (e)
{
storage.getPatch().isDirty = true;

for (int k = 0; k < n_osc_params; k++)
{
std::string oname = osc_st.p[k].oscName;
std::string sx = osc_st.p[k].get_name();

double d;
int j;
std::string lbl;

lbl = fmt::format("p{:d}", k);

if (storage.getPatch().scene[s].osc[i].p[k].valtype == vt_float)
if (osc_st.p[k].valtype == vt_float)
{
if (e->QueryDoubleAttribute(lbl.c_str(), &d) == TIXML_SUCCESS)
{
storage.getPatch().scene[s].osc[i].p[k].val.f = (float)d;
osc_st.p[k].val.f = (float)d;
for (const auto &it : audioThreadParamListeners)
(it.second)(oname, d, sx);
}
}
else
{
if (e->QueryIntAttribute(lbl.c_str(), &j) == TIXML_SUCCESS)
{
storage.getPatch().scene[s].osc[i].p[k].val.i = j;
osc_st.p[k].val.i = j;
for (const auto &it : audioThreadParamListeners)
(it.second)(oname, j, sx);
}
}

lbl = fmt::format("p{:d}_deform_type", k);

if (e->QueryIntAttribute(lbl.c_str(), &j) == TIXML_SUCCESS)
{
storage.getPatch().scene[s].osc[i].p[k].deform_type = j;
osc_st.p[k].deform_type = j;
for (const auto &it : audioThreadParamListeners)
(it.second)(oname, j, sx);
}

lbl = fmt::format("p{:d}_extend_range", k);

if (e->QueryIntAttribute(lbl.c_str(), &j) == TIXML_SUCCESS)
{
storage.getPatch().scene[s].osc[i].p[k].set_extend_range(j);
osc_st.p[k].set_extend_range(j);
for (const auto &it : audioThreadParamListeners)
(it.second)(oname, j, sx);
}
}

int rt;
if (e->QueryIntAttribute("retrigger", &rt) == TIXML_SUCCESS)
{
storage.getPatch().scene[s].osc[i].retrigger.val.b = rt;
osc_st.retrigger.val.b = rt;
std::stringstream sb;
sb << "/param/" << (char)('a' + s) << "/osc/" << i + 1 << "/retrigger";
std::string sx = rt > 0 ? "On" : "Off";
for (const auto &it : audioThreadParamListeners)
(it.second)(sb.str(), rt, sx);
}

/*
* Some oscillator types can change display when you change values
*/
if (storage.getPatch().scene[s].osc[i].type.val.i == ot_modern)
if (osc_st.type.val.i == ot_modern)
{
refresh_editor = true;
}
storage.getPatch().scene[s].osc[i].queue_xmldata = 0;
osc_st.queue_xmldata = 0;
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/common/SurgeSynthesizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -461,13 +461,13 @@ class alignas(16) SurgeSynthesizer
//==============================================================================
// Parameter changes coming from within the synth (e.g. from MIDI-learned input)
// are communicated to listeners here
std::unordered_map<std::string,
std::function<void(const std::string oscname, const float fval)>>
std::unordered_map<std::string, std::function<void(const std::string oscname, const float fval,
std::string valstr)>>
audioThreadParamListeners;

void
addAudioParamListener(std::string key,
std::function<void(const std::string oscname, const float fval)> const &l)
void addAudioParamListener(std::string key,
std::function<void(const std::string oscname, const float fval,
std::string valstr)> const &l)
{
audioThreadParamListeners.insert({key, l});
}
Expand Down
30 changes: 16 additions & 14 deletions src/surge-xt/osc/OpenSoundControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1282,20 +1282,22 @@ bool OpenSoundControl::initOSCOut(int port, std::string ipaddr)

// Add a listener for parameter changes that happen on the audio thread
// (e.g. MIDI-'learned' parameters being changed by incoming MIDI messages)
synth->addAudioParamListener("OSC_OUT", [this, ssp = sspPtr](std::string oname, float fval) {
assert(juce::MessageManager::getInstanceWithoutCreating());
auto *mm = juce::MessageManager::getInstanceWithoutCreating();
if (mm)
{
mm->callAsync(
[ssp, oname, fval]() { ssp->param_change_to_OSC(oname, 1, fval, 0., 0., ""); });
}
else
{
std::cerr << "The juce message manager is not running. You are misconfigured"
<< std::endl;
}
});
synth->addAudioParamListener(
"OSC_OUT", [this, ssp = sspPtr](std::string oname, float fval, std::string valstr) {
assert(juce::MessageManager::getInstanceWithoutCreating());
auto *mm = juce::MessageManager::getInstanceWithoutCreating();
if (mm)
{
mm->callAsync([ssp, oname, fval, valstr]() {
ssp->param_change_to_OSC(oname, 1, fval, 0., 0., valstr);
});
}
else
{
std::cerr << "The juce message manager is not running. You are misconfigured"
<< std::endl;
}
});

// Add a listener for modulation changes
synth->addModulationAPIListener(this);
Expand Down

0 comments on commit 51b9dc7

Please sign in to comment.