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 }