Skip to content

Commit

Permalink
Support DAW state in the patch (#1032)
Browse files Browse the repository at this point in the history
Some features of the synth - notably, zoom, MPE Enablement, and
Tuning - are features of the DAW environment you are working in and
were not persisted. This fixes that by adding a dawExtraState section
to the streaming protocol which is only populated and read at DAW time
not at general patch time.

Closes #890
Closes #914
CLoses #915
Mostly wraps up #828
  • Loading branch information
baconpaul authored Aug 16, 2019
1 parent 23c32d4 commit d3c0faa
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 18 deletions.
10 changes: 10 additions & 0 deletions src/au/aulayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,10 @@ ComponentResult aulayer::RestoreState(CFPropertyListRef plist)
p = CFDataGetBytePtr(data);
size_t psize = CFDataGetLength(data);
plugin_instance->loadRaw(p, psize, false);

plugin_instance->loadFromDawExtraState();
if( editor_instance )
editor_instance->loadFromDAWExtraState(plugin_instance);
}
return noErr;
}
Expand All @@ -545,11 +549,17 @@ ComponentResult aulayer::SaveState(CFPropertyListRef* plist)

CFMutableDictionaryRef dict = (CFMutableDictionaryRef)*plist;
void* data;

plugin_instance->populateDawExtraState();
if( editor_instance )
editor_instance->populateDawExtraState(plugin_instance);

CFIndex size = plugin_instance->saveRaw(&data);
CFDataRef dataref =
CFDataCreateWithBytesNoCopy(NULL, (const UInt8*)data, size, kCFAllocatorNull);
CFDictionarySetValue(dict, rawchunkname, dataref);
CFRelease(dataref);

return noErr;
}

Expand Down
181 changes: 176 additions & 5 deletions src/common/SurgePatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,15 @@ const int gui_mid_topbar_y = 17;
// 3 -> 4 comb+/- combined into 1 filtertype (subtype 0,0->0 0,1->1 1,0->2 1,1->3 )
// 4 -> 5 stereo filterconf now have seperate pan controls
// 5 -> 6 new filter sound in v1.2 (same parameters, but different sound & changed resonance
// response). 6 -> 7 custom controller state now stored (in seq. recall) 7 -> 8 larger resonance
// range (old filters are set to subtype 1), pan2 -> width 8 -> 9 now 8 controls (offset ids larger
// than ctrl7 by +1), custom controllers have names (guess for pre-rev9 patches) 9 -> 10 added
// character parameter
const int ff_revision = 10;
// response).
// 6 -> 7 custom controller state now stored (in seq. recall)
// 7 -> 8 larger resonance
// range (old filters are set to subtype 1), pan2 -> width
// 8 -> 9 now 8 controls (offset ids larger
// than ctrl7 by +1), custom controllers have names (guess for pre-rev9 patches)
// 9 -> 10 added character parameter
// 10 -> 11 (1.6.2 release) added DAW Extra State
const int ff_revision = 11;

SurgePatch::SurgePatch(SurgeStorage* storage)
{
Expand Down Expand Up @@ -732,6 +736,96 @@ struct patch_header
};
#pragma pack(pop)


// BASE 64 SUPPORT. THANKS https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";


static inline bool is_base64(unsigned char c) {
return (isalnum(c) || (c == '+') || (c == '/'));
}

std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
std::string ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];

while (in_len--) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;

for(i = 0; (i <4) ; i++)
ret += base64_chars[char_array_4[i]];
i = 0;
}
}

if (i)
{
for(j = i; j < 3; j++)
char_array_3[j] = '\0';

char_array_4[0] = ( char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);

for (j = 0; (j < i + 1); j++)
ret += base64_chars[char_array_4[j]];

while((i++ < 3))
ret += '=';

}

return ret;
}

std::string base64_decode(std::string const& encoded_string) {
int in_len = encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;

while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
char_array_4[i++] = encoded_string[in_]; in_++;
if (i ==4) {
for (i = 0; i <4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);

char_array_3[0] = ( char_array_4[0] << 2 ) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

for (i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
}
}

if (i) {
for (j = 0; j < i; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);

char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);

for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
}

return ret;
}

void SurgePatch::load_patch(const void* data, int datasize, bool preset)
{
if (datasize <= 4)
Expand Down Expand Up @@ -878,6 +972,7 @@ void SurgePatch::load_xml(const void* data, int datasize, bool is_preset)
char* temp = (char*)malloc(datasize + 1);
memcpy(temp, data, datasize);
*(temp + datasize) = 0;
// std::cout << "XML DOC is " << temp << std::endl;
doc.Parse(temp, nullptr, TIXML_ENCODING_LEGACY);
free(temp);
}
Expand Down Expand Up @@ -1259,6 +1354,53 @@ void SurgePatch::load_xml(const void* data, int datasize, bool is_preset)
}
p = TINYXML_SAFE_TO_ELEMENT(p->NextSibling("entry"));
}

dawExtraState.isPopulated = false;
TiXmlElement *de = TINYXML_SAFE_TO_ELEMENT(patch->FirstChild("dawExtraState"));
if( de )
{
int pop;
if( de->QueryIntAttribute("populated", &pop) == TIXML_SUCCESS )
{
dawExtraState.isPopulated = (pop != 0);
}

if( dawExtraState.isPopulated )
{
int ival;
TiXmlElement *p;
p = TINYXML_SAFE_TO_ELEMENT(de->FirstChild("instanceZoomFactor"));
if( p &&
p->QueryIntAttribute("v",&ival) == TIXML_SUCCESS)
dawExtraState.instanceZoomFactor = ival;

p = TINYXML_SAFE_TO_ELEMENT(de->FirstChild("mpeEnabled"));
if( p &&
p->QueryIntAttribute("v",&ival) == TIXML_SUCCESS)
dawExtraState.mpeEnabled = ival;

p = TINYXML_SAFE_TO_ELEMENT(de->FirstChild("hasTuning"));
if( p &&
p->QueryIntAttribute("v",&ival) == TIXML_SUCCESS)
{
dawExtraState.hasTuning = (ival != 0);
}

const char* td;
if( dawExtraState.hasTuning )
{
p = TINYXML_SAFE_TO_ELEMENT(de->FirstChild("tuningContents"));
if( p &&
(td = p->Attribute("v") ))
{
auto tc = base64_decode(td);
dawExtraState.tuningContents = tc;
}
}

}
}

if (!is_preset)
{
TiXmlElement* mw = TINYXML_SAFE_TO_ELEMENT(patch->FirstChild("modwheel"));
Expand Down Expand Up @@ -1437,6 +1579,35 @@ unsigned int SurgePatch::save_xml(void** data) // allocates mem, must be freed b
patch.InsertEndChild(mw);
}

TiXmlElement dawExtraXML("dawExtraState");
dawExtraXML.SetAttribute( "populated", dawExtraState.isPopulated ? 1 : 0 );

if( dawExtraState.isPopulated )
{
TiXmlElement izf("instanceZoomFactor");
izf.SetAttribute( "v", dawExtraState.instanceZoomFactor );
dawExtraXML.InsertEndChild(izf);

TiXmlElement mpe("mpeEnabled");
mpe.SetAttribute("v", dawExtraState.mpeEnabled ? 1 : 0 );
dawExtraXML.InsertEndChild(mpe);

TiXmlElement tun("hasTuning");
tun.SetAttribute("v", dawExtraState.hasTuning ? 1 : 0 );
dawExtraXML.InsertEndChild(tun);

/*
** we really want a cdata here but TIXML is ambiguous whether
** it does the right thing when I read the code, and is kinda crufty
** so just protect ourselves with a base 64 encoding.
*/
TiXmlElement tnc("tuningContents");
tnc.SetAttribute("v", base64_encode( (unsigned const char *)dawExtraState.tuningContents.c_str(),
dawExtraState.tuningContents.size() ).c_str() );
dawExtraXML.InsertEndChild(tnc);
}
patch.InsertEndChild(dawExtraXML);

doc.InsertEndChild(decl);
doc.InsertEndChild(patch);

Expand Down
17 changes: 17 additions & 0 deletions src/common/SurgeStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,21 @@ struct StepSequencerStorage
unsigned int trigmask;
};

/*
** There are a collection of things we want your DAW to save about your particular instance
** but don't want saved in your patch. So have this extra structure in the patch which we
** can activate/populate from the DAW hosts. See #915
*/
struct DAWExtraStateStorage
{
bool isPopulated = false;

int instanceZoomFactor = -1;
bool mpeEnabled = false;
bool hasTuning = false;
std::string tuningContents = "";
};

class SurgeStorage;

class SurgePatch
Expand Down Expand Up @@ -423,6 +438,8 @@ class SurgePatch

StepSequencerStorage stepsequences[2][n_lfos];

DAWExtraStateStorage dawExtraState;

std::vector<Parameter*> param_ptr;
std::vector<int> easy_params_id;

Expand Down
20 changes: 20 additions & 0 deletions src/common/SurgeSynthesizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,26 @@ class alignas(16) SurgeSynthesizer

float vu_peak[8];

void populateDawExtraState() {
storage.getPatch().dawExtraState.isPopulated = true;
storage.getPatch().dawExtraState.mpeEnabled = mpeEnabled;
storage.getPatch().dawExtraState.hasTuning = !storage.isStandardTuning;
if( ! storage.isStandardTuning )
storage.getPatch().dawExtraState.tuningContents = storage.currentScale.rawText;
else
storage.getPatch().dawExtraState.tuningContents = "";
}
void loadFromDawExtraState() {
if( ! storage.getPatch().dawExtraState.isPopulated )
return;
mpeEnabled = storage.getPatch().dawExtraState.mpeEnabled;
if( storage.getPatch().dawExtraState.hasTuning )
{
auto sc = Surge::Storage::parseSCLData(storage.getPatch().dawExtraState.tuningContents );
storage.retuneToScale(sc);
}
}

public:
int CC0, PCH, patchid;
float masterfade = 0;
Expand Down
40 changes: 27 additions & 13 deletions src/common/Tunings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,13 @@
should give a read error and be rejected.
*/

Surge::Storage::Scale Surge::Storage::readSCLFile(std::string fname)
Surge::Storage::Scale scaleFromStream(std::istream &inf)
{
std::ifstream inf;
inf.open(fname);
if (!inf.is_open())
{
return Scale();
}

std::string line;
const int read_header = 0, read_count = 1, read_note = 2;
int state = read_header;

Scale res;
res.name = fname;
Surge::Storage::Scale res;
std::ostringstream rawOSS;
while (std::getline(inf, line))
{
Expand All @@ -65,16 +57,16 @@ Surge::Storage::Scale Surge::Storage::readSCLFile(std::string fname)
state = read_note;
break;
case read_note:
Tone t;
Surge::Storage::Tone t;
t.stringRep = line;
if (line.find(".") != std::string::npos)
{
t.type = Tone::kToneCents;
t.type = Surge::Storage::Tone::kToneCents;
t.cents = atof(line.c_str());
}
else
{
t.type = Tone::kToneRatio;
t.type = Surge::Storage::Tone::kToneRatio;
auto slashPos = line.find("/");
if (slashPos == std::string::npos)
{
Expand Down Expand Up @@ -103,6 +95,28 @@ Surge::Storage::Scale Surge::Storage::readSCLFile(std::string fname)
return res;
}

Surge::Storage::Scale Surge::Storage::readSCLFile(std::string fname)
{
std::ifstream inf;
inf.open(fname);
if (!inf.is_open())
{
return Scale();
}

auto res = scaleFromStream(inf);
res.name = fname;
return res;
}

Surge::Storage::Scale Surge::Storage::parseSCLData(const std::string &d)
{
std::istringstream iss(d);
auto res = scaleFromStream(iss);
res.name = "Scale from Patch";
return res;
}

std::ostream& Surge::Storage::operator<<(std::ostream& os, const Surge::Storage::Tone& t)
{
os << (t.type == Tone::kToneCents ? "cents" : "ratio") << " ";
Expand Down
1 change: 1 addition & 0 deletions src/common/Tunings.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ std::ostream& operator<<(std::ostream& os, const Tone& sc);
std::ostream& operator<<(std::ostream& os, const Scale& sc);

Scale readSCLFile(std::string fname);
Scale parseSCLData(const std::string &sclContents);
} // namespace Storage
} // namespace Surge
8 changes: 8 additions & 0 deletions src/common/gui/SurgeGUIEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ SurgeGUIEditor::SurgeGUIEditor(void* effect, SurgeSynthesizer* synth) : super(ef
// init the size of the plugin
int userDefaultZoomFactor = Surge::Storage::getUserDefaultValue(&(synth->storage), "defaultZoom", 100);
float zf = userDefaultZoomFactor / 100.0;

if( synth->storage.getPatch().dawExtraState.isPopulated &&
synth->storage.getPatch().dawExtraState.instanceZoomFactor > 0
)
{
// If I restore state before I am constructed I need to do this
zf = synth->storage.getPatch().dawExtraState.instanceZoomFactor / 100.0;
}

rect.left = 0;
rect.top = 0;
Expand Down
Loading

0 comments on commit d3c0faa

Please sign in to comment.