Skip to content

Commit

Permalink
Path to Persistent Midi Mapping 1: Save in DAW State (#2073)
Browse files Browse the repository at this point in the history
With this change, if your surge sets up a MIDI mapping for
either a param or a custom controller, it is saved and retrieved
from the DAW state. This means that learn / save in REAPEr or Logic or
what not / quit / restore and that learn sticks.

Closes #1774
Addresses #1678
Addresses #1994
  • Loading branch information
baconpaul committed Jun 8, 2020
1 parent cb18e03 commit a293503
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 60 deletions.
53 changes: 53 additions & 0 deletions src/common/SurgePatch.cpp
Expand Up @@ -1651,6 +1651,36 @@ void SurgePatch::load_xml(const void* data, int datasize, bool is_preset)
}
}

p = TINYXML_SAFE_TO_ELEMENT(de->FirstChild("midictrl_map"));
if( p )
{
auto c = TINYXML_SAFE_TO_ELEMENT(p->FirstChild( "c" ) );
while( c )
{
int p, v;
if( c->QueryIntAttribute( "p", &p ) == TIXML_SUCCESS &&
c->QueryIntAttribute( "v", &v ) == TIXML_SUCCESS )
{
dawExtraState.midictrl_map[p] = v;
}
c = TINYXML_SAFE_TO_ELEMENT(c->NextSibling( "c" ) );
}
}

p = TINYXML_SAFE_TO_ELEMENT(de->FirstChild("customcontrol_map"));
if( p )
{
auto c = TINYXML_SAFE_TO_ELEMENT(p->FirstChild( "c" ) );
while( c )
{
int p, v;
if( c->QueryIntAttribute( "p", &p ) == TIXML_SUCCESS &&
c->QueryIntAttribute( "v", &v ) == TIXML_SUCCESS )
dawExtraState.customcontrol_map[p] = v;
c = TINYXML_SAFE_TO_ELEMENT(c->NextSibling( "c" ) );
}
}

}
}

Expand Down Expand Up @@ -1922,6 +1952,29 @@ unsigned int SurgePatch::save_xml(void** data) // allocates mem, must be freed b
mpc.SetAttribute("v", base64_encode( (unsigned const char *)dawExtraState.mappingContents.c_str(),
dawExtraState.mappingContents.size() ).c_str() );
dawExtraXML.InsertEndChild(mpc);

/*
** Add the midi controls
*/
TiXmlElement mcm("midictrl_map");
for( auto &p : dawExtraState.midictrl_map )
{
TiXmlElement c("c");
c.SetAttribute( "p", p.first );
c.SetAttribute( "v", p.second );
mcm.InsertEndChild(c);
}
dawExtraXML.InsertEndChild(mcm);

TiXmlElement ccm("customcontrol_map");
for( auto &p : dawExtraState.customcontrol_map )
{
TiXmlElement c("c");
c.SetAttribute( "p", p.first );
c.SetAttribute( "v", p.second );
ccm.InsertEndChild(c);
}
dawExtraXML.InsertEndChild(ccm);
}
patch.InsertEndChild(dawExtraXML);

Expand Down
4 changes: 4 additions & 0 deletions src/common/SurgeStorage.h
Expand Up @@ -22,6 +22,7 @@
#include <fstream>
#include <iterator>
#include <functional>
#include <unordered_map>

#include "Tunings.h"

Expand Down Expand Up @@ -406,6 +407,9 @@ struct DAWExtraStateStorage

bool hasMapping = false;
std::string mappingContents = "";

std::unordered_map<int, int> midictrl_map; // param -> midictrl
std::unordered_map<int, int> customcontrol_map; // custom controller number -> midicontrol
};


Expand Down
99 changes: 99 additions & 0 deletions src/common/SurgeSynthesizer.cpp
Expand Up @@ -2859,3 +2859,102 @@ PluginLayer* SurgeSynthesizer::getParent()
assert(_parent != nullptr);
return _parent;
}

void SurgeSynthesizer::populateDawExtraState() {
storage.getPatch().dawExtraState.isPopulated = true;
storage.getPatch().dawExtraState.mpeEnabled = mpeEnabled;
storage.getPatch().dawExtraState.mpePitchBendRange = mpePitchBendRange;

storage.getPatch().dawExtraState.hasTuning = !storage.isStandardTuning;
if( ! storage.isStandardTuning )
storage.getPatch().dawExtraState.tuningContents = storage.currentScale.rawText;
else
storage.getPatch().dawExtraState.tuningContents = "";

storage.getPatch().dawExtraState.hasMapping = !storage.isStandardMapping;
if( ! storage.isStandardMapping )
storage.getPatch().dawExtraState.mappingContents = storage.currentMapping.rawText;
else
storage.getPatch().dawExtraState.mappingContents = "";

int n = n_global_params + n_scene_params; // only store midictrl's for scene A (scene A -> scene
// B will be duplicated on load)
for (int i = 0; i < n; i++)
{
if (storage.getPatch().param_ptr[i]->midictrl >= 0)
{
storage.getPatch().dawExtraState.midictrl_map[i] = storage.getPatch().param_ptr[i]->midictrl;
}
}

for (int i=0; i<n_customcontrollers; ++i )
{
storage.getPatch().dawExtraState.customcontrol_map[i] = storage.controllers[i];
}

}

void SurgeSynthesizer::loadFromDawExtraState() {
if( ! storage.getPatch().dawExtraState.isPopulated )
return;
mpeEnabled = storage.getPatch().dawExtraState.mpeEnabled;
if( storage.getPatch().dawExtraState.mpePitchBendRange > 0 )
mpePitchBendRange = storage.getPatch().dawExtraState.mpePitchBendRange;

if( storage.getPatch().dawExtraState.hasTuning )
{
try {
auto sc = Tunings::parseSCLData(storage.getPatch().dawExtraState.tuningContents );
storage.retuneToScale(sc);
}
catch( Tunings::TuningError &e )
{
Surge::UserInteractions::promptError( e.what(), "Unable to restore tuning" );
storage.retuneToStandardTuning();
}
}
else
{
storage.retuneToStandardTuning();
}

if( storage.getPatch().dawExtraState.hasMapping )
{
try
{
auto kb = Tunings::parseKBMData(storage.getPatch().dawExtraState.mappingContents );
storage.remapToKeyboard(kb);
}
catch( Tunings::TuningError &e )
{
Surge::UserInteractions::promptError( e.what(), "Unable to restore mapping" );
storage.retuneToStandardTuning();
}

}
else
{
storage.remapToStandardKeyboard();
}

int n = n_global_params + n_scene_params; // only store midictrl's for scene A (scene A -> scene
// B will be duplicated on load)
for (int i = 0; i < n; i++)
{
if (storage.getPatch().dawExtraState.midictrl_map.find(i) != storage.getPatch().dawExtraState.midictrl_map.end() )
{
storage.getPatch().param_ptr[i]->midictrl = storage.getPatch().dawExtraState.midictrl_map[i];
if( i >= n_global_params )
{
storage.getPatch().param_ptr[i + n_scene_params]->midictrl = storage.getPatch().dawExtraState.midictrl_map[i];
}
}
}

for (int i=0; i<n_customcontrollers; ++i )
{
if( storage.getPatch().dawExtraState.customcontrol_map.find(i) != storage.getPatch().dawExtraState.midictrl_map.end() )
storage.controllers[i] = storage.getPatch().dawExtraState.customcontrol_map[i];
}

}
62 changes: 2 additions & 60 deletions src/common/SurgeSynthesizer.h
Expand Up @@ -178,67 +178,9 @@ class alignas(16) SurgeSynthesizer

float vu_peak[8];

void populateDawExtraState() {
storage.getPatch().dawExtraState.isPopulated = true;
storage.getPatch().dawExtraState.mpeEnabled = mpeEnabled;
storage.getPatch().dawExtraState.mpePitchBendRange = mpePitchBendRange;

storage.getPatch().dawExtraState.hasTuning = !storage.isStandardTuning;
if( ! storage.isStandardTuning )
storage.getPatch().dawExtraState.tuningContents = storage.currentScale.rawText;
else
storage.getPatch().dawExtraState.tuningContents = "";

storage.getPatch().dawExtraState.hasMapping = !storage.isStandardMapping;
if( ! storage.isStandardMapping )
storage.getPatch().dawExtraState.mappingContents = storage.currentMapping.rawText;
else
storage.getPatch().dawExtraState.mappingContents = "";
}
void populateDawExtraState();

void loadFromDawExtraState() {
if( ! storage.getPatch().dawExtraState.isPopulated )
return;
mpeEnabled = storage.getPatch().dawExtraState.mpeEnabled;
if( storage.getPatch().dawExtraState.mpePitchBendRange > 0 )
mpePitchBendRange = storage.getPatch().dawExtraState.mpePitchBendRange;

if( storage.getPatch().dawExtraState.hasTuning )
{
try {
auto sc = Tunings::parseSCLData(storage.getPatch().dawExtraState.tuningContents );
storage.retuneToScale(sc);
}
catch( Tunings::TuningError &e )
{
Surge::UserInteractions::promptError( e.what(), "Unable to restore tuning" );
storage.retuneToStandardTuning();
}
}
else
{
storage.retuneToStandardTuning();
}

if( storage.getPatch().dawExtraState.hasMapping )
{
try
{
auto kb = Tunings::parseKBMData(storage.getPatch().dawExtraState.mappingContents );
storage.remapToKeyboard(kb);
}
catch( Tunings::TuningError &e )
{
Surge::UserInteractions::promptError( e.what(), "Unable to restore mapping" );
storage.retuneToStandardTuning();
}

}
else
{
storage.remapToStandardKeyboard();
}
}
void loadFromDawExtraState();

public:
int CC0, CC32, PCH, patchid;
Expand Down
76 changes: 76 additions & 0 deletions src/headless/UnitTestsIO.cpp
Expand Up @@ -232,6 +232,82 @@ TEST_CASE( "DAW Streaming and Unstreaming", "[io][mpe][tun]" )

}

SECTION( "Save and Restore Param Midi Controls - Simple" )
{
auto surgeSrc = Surge::Headless::createSurge(44100);
auto surgeDest = Surge::Headless::createSurge(44100);

// Simplest case
surgeSrc->storage.getPatch().param_ptr[118]->midictrl = 57;
REQUIRE( surgeSrc->storage.getPatch().param_ptr[118]->midictrl == 57 );
REQUIRE( surgeDest->storage.getPatch().param_ptr[118]->midictrl != 57 );

fromto( surgeSrc, surgeDest );
REQUIRE( surgeSrc->storage.getPatch().param_ptr[118]->midictrl == 57 );
REQUIRE( surgeDest->storage.getPatch().param_ptr[118]->midictrl == 57 );
}

SECTION( "Save and Restore Param Midi Controls - Empty" )
{
auto surgeSrc = Surge::Headless::createSurge(44100);
auto surgeDest = Surge::Headless::createSurge(44100);

fromto( surgeSrc, surgeDest );
for( int i=0; i<n_global_params + n_scene_params; ++i )
{
REQUIRE( surgeSrc->storage.getPatch().param_ptr[i]->midictrl ==
surgeDest->storage.getPatch().param_ptr[i]->midictrl );
REQUIRE( surgeSrc->storage.getPatch().param_ptr[i]->midictrl == -1 );
}
}

SECTION( "Save and Restore Param Midi Controls - Multi" )
{
auto surgeSrc = Surge::Headless::createSurge(44100);
auto surgeDest = Surge::Headless::createSurge(44100);

// Bigger Case
surgeSrc->storage.getPatch().param_ptr[118]->midictrl = 57;
surgeSrc->storage.getPatch().param_ptr[123]->midictrl = 59;
surgeSrc->storage.getPatch().param_ptr[172]->midictrl = 82;
REQUIRE( surgeSrc->storage.getPatch().param_ptr[118]->midictrl == 57 );
REQUIRE( surgeSrc->storage.getPatch().param_ptr[123]->midictrl == 59 );
REQUIRE( surgeSrc->storage.getPatch().param_ptr[172]->midictrl == 82 );
REQUIRE( surgeDest->storage.getPatch().param_ptr[118]->midictrl != 57 );
REQUIRE( surgeDest->storage.getPatch().param_ptr[123]->midictrl != 59 );
REQUIRE( surgeDest->storage.getPatch().param_ptr[172]->midictrl != 82 );

fromto( surgeSrc, surgeDest );
REQUIRE( surgeSrc->storage.getPatch().param_ptr[118]->midictrl == 57 );
REQUIRE( surgeSrc->storage.getPatch().param_ptr[123]->midictrl == 59 );
REQUIRE( surgeSrc->storage.getPatch().param_ptr[172]->midictrl == 82 );
REQUIRE( surgeDest->storage.getPatch().param_ptr[118]->midictrl == 57 );
REQUIRE( surgeDest->storage.getPatch().param_ptr[123]->midictrl == 59 );
REQUIRE( surgeDest->storage.getPatch().param_ptr[172]->midictrl == 82 );
}

SECTION( "Save and Restore Custom Controllers" )
{
auto surgeSrc = Surge::Headless::createSurge(44100);
auto surgeDest = Surge::Headless::createSurge(44100);

for( int i=0; i<n_customcontrollers; ++i )
{
REQUIRE( surgeSrc->storage.controllers[i] == 41 + i );
REQUIRE( surgeDest->storage.controllers[i] == 41 + i );
}

surgeSrc->storage.controllers[2] = 75;
surgeSrc->storage.controllers[4] = 79;
fromto(surgeSrc, surgeDest );
for( int i=0; i<n_customcontrollers; ++i )
{
REQUIRE( surgeSrc->storage.controllers[i] == surgeDest->storage.controllers[i] );
}
REQUIRE( surgeDest->storage.controllers[2] == 75 );
REQUIRE( surgeDest->storage.controllers[4] == 79 );
}

}

TEST_CASE( "Stream WaveTable Names", "[io]" )
Expand Down

0 comments on commit a293503

Please sign in to comment.