gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

clipboard.cpp (5708B)


      1 #include "clipboard.h"
      2 
      3 #include "synthLib/midiToSysex.h"
      4 
      5 #include <sstream>
      6 
      7 #include "pluginVersion.h"
      8 #include "processor.h"
      9 
     10 #include "baseLib/filesystem.h"
     11 
     12 #include "dsp56kEmu/logging.h"
     13 
     14 #include "juce_core/juce_core.h"
     15 
     16 namespace pluginLib
     17 {
     18 	std::string Clipboard::midiDataToString(const std::vector<uint8_t>& _data, const uint32_t _bytesPerLine/* = 32*/)
     19 	{
     20 		if(_data.empty())
     21 			return {};
     22 
     23 		std::stringstream ss;
     24 
     25 		for(size_t i=0; i<_data.size();)
     26 		{
     27 			if(i)
     28 				ss << '\n';
     29 			for(size_t j=0; j<_bytesPerLine && i<_data.size(); ++j, ++i)
     30 			{
     31 				if(j)
     32 					ss << ' ';
     33 				ss << HEXN(static_cast<uint32_t>(_data[i]), 2);
     34 			}
     35 		}
     36 
     37 		return ss.str();
     38 	}
     39 
     40 	std::vector<uint8_t> Clipboard::getSysexFromString(const std::string& _text)
     41 	{
     42 		if(_text.empty())
     43 			return {};
     44 
     45 		auto text = baseLib::filesystem::lowercase(_text);
     46 
     47 		while(true)
     48 		{
     49 			const auto pos = text.find_first_of(" \n\r\t");
     50 			if(pos == std::string::npos)
     51 				break;
     52 			text = text.substr(0,pos) + text.substr(pos+1);
     53 		}
     54 
     55 		const auto posF0 = text.find("f0");
     56 		if(posF0 == std::string::npos)
     57 			return {};
     58 
     59 		const auto posF7 = text.rfind("f7");
     60 		if(posF7 == std::string::npos)
     61 			return {};
     62 
     63 		if(posF7 <= posF0)
     64 			return {};
     65 
     66 		const auto dataString = text.substr(posF0, posF7 + 2 - posF0);
     67 
     68 		if(dataString.size() & 1)
     69 			return {};
     70 
     71 		std::vector<uint8_t> data;
     72 		data.reserve(dataString.size()>>1);
     73 
     74 		for(size_t i=0; i<dataString.size(); i+=2)
     75 		{
     76 			char temp[3]{0,0,0};
     77 			temp[0] = dataString[i];
     78 			temp[1] = dataString[i+1];
     79 
     80 			const auto c = strtoul(temp, nullptr, 16);
     81 			if(c < 0 || c > 255)
     82 				return {};
     83 			data.push_back(static_cast<uint8_t>(c));
     84 		}
     85 
     86 		return data;
     87 	}
     88 
     89 	std::string Clipboard::parametersToString(Processor& _processor, const std::vector<std::string>& _parameters, const std::string& _regionId)
     90 	{
     91 		if(_parameters.empty())
     92 			return {};
     93 
     94 		return createJsonString(_processor, _parameters, _regionId, {});
     95 	}
     96 
     97 	std::string Clipboard::createJsonString(Processor& _processor, const std::vector<std::string>& _parameters, const std::string& _regionId, const std::vector<uint8_t>& _sysex)
     98 	{
     99 		if(_parameters.empty() && _sysex.empty())
    100 			return {};
    101 
    102 		const auto json = juce::ReferenceCountedObjectPtr<juce::DynamicObject>(new juce::DynamicObject());
    103 
    104 		json->setProperty("plugin", juce::String(_processor.getProperties().name));
    105 		json->setProperty("pluginVersion", juce::String(Version::getVersionString()));
    106 		json->setProperty("pluginVersionNumber", juce::String(Version::getVersionNumber()));
    107 
    108 		json->setProperty("formatVersion", 1);
    109 
    110 		if(!_regionId.empty())
    111 			json->setProperty("region", juce::String(_regionId));
    112 
    113 		const auto& c = _processor.getController();
    114 		const auto part = c.getCurrentPart();
    115 
    116 		if(!_parameters.empty())
    117 		{
    118 			const auto params = juce::ReferenceCountedObjectPtr<juce::DynamicObject>(new juce::DynamicObject());
    119 
    120 			for (const auto& param : _parameters)
    121 			{
    122 				const auto* p = c.getParameter(param, part);
    123 
    124 				if(!p)
    125 					continue;
    126 
    127 				params->setProperty(juce::String(p->getDescription().name), p->getUnnormalizedValue());
    128 			}
    129 
    130 			json->setProperty("parameters", params.get());
    131 		}
    132 
    133 		if(!_sysex.empty())
    134 			json->setProperty("sysex", juce::String(midiDataToString(_sysex, static_cast<uint32_t>(_sysex.size()))));
    135 
    136 		const auto result = juce::JSON::toString(json.get());
    137 
    138 		return result.toStdString();
    139 	}
    140 
    141 	Clipboard::Data Clipboard::getDataFromString(Processor& _processor, const std::string& _text)
    142 	{
    143 		if(_text.empty())
    144 			return {};
    145 
    146 		const auto json = juce::JSON::parse(juce::String(_text));
    147 
    148 		Data data;
    149 
    150 		auto parseRawMidi = [&]()
    151 		{
    152 			data.sysex = getSysexFromString(_text);
    153 			return data;
    154 		};
    155 
    156 		data.pluginName = json["plugin"].toString().toStdString();
    157 
    158 		// for this plugin or another plugin?
    159 		if(data.pluginName != _processor.getProperties().name)
    160 			return parseRawMidi();
    161 
    162 		data.pluginVersionNumber = static_cast<int>(json["pluginVersionNumber"]);
    163 
    164 		// version cannot be lower than when we first added the feature
    165 		if(data.pluginVersionNumber < 10315)
    166 			return parseRawMidi();
    167 
    168 		data.formatVersion = static_cast<int>(json["formatVersion"]);
    169 
    170 		// we only support version 1 atm
    171 		if(data.formatVersion != 1)
    172 			return parseRawMidi();
    173 
    174 		data.pluginVersionString = json["pluginVersion"].toString().toStdString();
    175 
    176 		data.parameterRegion = json["region"].toString().toStdString();
    177 
    178 		const auto* params = json["parameters"].getDynamicObject();
    179 
    180 		if(params)
    181 		{
    182 			const auto& c = _processor.getController();
    183 
    184 			const auto& props = params->getProperties();
    185 			for (const auto& it : props)
    186 			{
    187 				const auto name = it.name.toString().toStdString();
    188 				const int value = it.value;
    189 
    190 				// something is very wrong if a parameter exists twice
    191 				if(data.parameterValues.find(name) != data.parameterValues.end())
    192 					return parseRawMidi();
    193 
    194 				// also if a parameter value is out of range
    195 				if(value < 0 || value > 127)
    196 					return parseRawMidi();
    197 
    198 				// gracefully ignore parameters that we are not aware of. Parameters might change in the future or whatever
    199 				if(c.getParameterIndexByName(name) == Controller::InvalidParameterIndex)
    200 					continue;
    201 
    202 				data.parameterValues.insert(std::make_pair(name, static_cast<uint8_t>(value)));
    203 			}
    204 
    205 			if(!data.parameterValues.empty())
    206 			{
    207 				for (const auto& it : data.parameterValues)
    208 				{
    209 					const auto& paramName = it.first;
    210 
    211 					const auto regionIds = c.getRegionIdsForParameter(paramName);
    212 
    213 					for (const auto& regionId : regionIds)
    214 						data.parameterValuesByRegion[regionId].insert(it);
    215 				}
    216 			}
    217 		}
    218 
    219 		const auto sysex = json["sysex"].toString().toStdString();
    220 
    221 		if(!sysex.empty())
    222 			data.sysex = getSysexFromString(sysex);
    223 
    224 		return data;
    225 	}
    226 }