Storing programs

pull/1/head
asb2m10 11 years ago
parent 7211900796
commit 26f049653b
  1. BIN
      Builds/MacOSX/Dexed.xcodeproj/project.xcworkspace/xcuserdata/asb2m10.xcuserdatad/UserInterfaceState.xcuserstate
  2. 96
      Source/OperatorEditor.cpp
  3. 92
      Source/PluginEditor.cpp
  4. 2
      Source/PluginEditor.h
  5. 68
      Source/PluginFx.cpp
  6. 79
      Source/PluginParam.cpp
  7. 9
      Source/PluginProcessor.h

@ -360,13 +360,13 @@ void OperatorEditor::sliderValueChanged (Slider* sliderThatWasMoved)
else if (sliderThatWasMoved == opFine) else if (sliderThatWasMoved == opFine)
{ {
//[UserSliderCode_opFine] -- add your slider handling code here.. //[UserSliderCode_opFine] -- add your slider handling code here..
updateDisplay(); updateDisplay();
//[/UserSliderCode_opFine] //[/UserSliderCode_opFine]
} }
else if (sliderThatWasMoved == opCoarse) else if (sliderThatWasMoved == opCoarse)
{ {
//[UserSliderCode_opCoarse] -- add your slider handling code here.. //[UserSliderCode_opCoarse] -- add your slider handling code here..
updateDisplay(); updateDisplay();
//[/UserSliderCode_opCoarse] //[/UserSliderCode_opCoarse]
} }
else if (sliderThatWasMoved == gain) else if (sliderThatWasMoved == gain)
@ -377,7 +377,7 @@ void OperatorEditor::sliderValueChanged (Slider* sliderThatWasMoved)
else if (sliderThatWasMoved == detune) else if (sliderThatWasMoved == detune)
{ {
//[UserSliderCode_detune] -- add your slider handling code here.. //[UserSliderCode_detune] -- add your slider handling code here..
updateDisplay(); updateDisplay();
//[/UserSliderCode_detune] //[/UserSliderCode_detune]
} }
else if (sliderThatWasMoved == sclLeftLevel) else if (sliderThatWasMoved == sclLeftLevel)
@ -423,7 +423,7 @@ void OperatorEditor::comboBoxChanged (ComboBox* comboBoxThatHasChanged)
if (comboBoxThatHasChanged == opMode) if (comboBoxThatHasChanged == opMode)
{ {
//[UserComboBoxCode_opMode] -- add your combo box handling code here.. //[UserComboBoxCode_opMode] -- add your combo box handling code here..
updateDisplay(); updateDisplay();
//[/UserComboBoxCode_opMode] //[/UserComboBoxCode_opMode]
} }
else if (comboBoxThatHasChanged == kbdLeftCurve) else if (comboBoxThatHasChanged == kbdLeftCurve)
@ -445,30 +445,30 @@ void OperatorEditor::comboBoxChanged (ComboBox* comboBoxThatHasChanged)
//[MiscUserCode] You can add your own definitions of your custom methods or any other code here... //[MiscUserCode] You can add your own definitions of your custom methods or any other code here...
void OperatorEditor::bind(DexedAudioProcessor *parent, int op) { void OperatorEditor::bind(DexedAudioProcessor *parent, int op) {
int targetNum = op+1; int targetNum = op+1;
String opName; String opName;
opName << "OP" << targetNum; opName << "OP" << targetNum;
opId->setText(opName, NotificationType::dontSendNotification); opId->setText(opName, NotificationType::dontSendNotification);
parent->opCtrl[op].egLevel[0]->bind(s_egl1); parent->opCtrl[op].egLevel[0]->bind(s_egl1);
parent->opCtrl[op].egLevel[1]->bind(s_egl2); parent->opCtrl[op].egLevel[1]->bind(s_egl2);
parent->opCtrl[op].egLevel[2]->bind(s_egl3); parent->opCtrl[op].egLevel[2]->bind(s_egl3);
parent->opCtrl[op].egLevel[3]->bind(s_egl4); parent->opCtrl[op].egLevel[3]->bind(s_egl4);
parent->opCtrl[op].egRate[0]->bind(s_egv1); parent->opCtrl[op].egRate[0]->bind(s_egv1);
parent->opCtrl[op].egRate[1]->bind(s_egv2); parent->opCtrl[op].egRate[1]->bind(s_egv2);
parent->opCtrl[op].egRate[2]->bind(s_egv3); parent->opCtrl[op].egRate[2]->bind(s_egv3);
parent->opCtrl[op].egRate[3]->bind(s_egv4); parent->opCtrl[op].egRate[3]->bind(s_egv4);
parent->opCtrl[op].level->bind(opLevel); parent->opCtrl[op].level->bind(opLevel);
parent->opCtrl[op].opMode->bind(opMode); parent->opCtrl[op].opMode->bind(opMode);
parent->opCtrl[op].fine->bind(opFine); parent->opCtrl[op].fine->bind(opFine);
parent->opCtrl[op].coarse->bind(opCoarse); parent->opCtrl[op].coarse->bind(opCoarse);
parent->opCtrl[op].detune->bind(detune); parent->opCtrl[op].detune->bind(detune);
parent->opCtrl[op].sclBrkPt->bind(sclLvlBrkPt); parent->opCtrl[op].sclBrkPt->bind(sclLvlBrkPt);
parent->opCtrl[op].sclLeftCurve->bind(kbdLeftCurve); parent->opCtrl[op].sclLeftCurve->bind(kbdLeftCurve);
parent->opCtrl[op].sclRightCurve->bind(kbdRightCurve); parent->opCtrl[op].sclRightCurve->bind(kbdRightCurve);
parent->opCtrl[op].sclLeftDepth->bind(sclLeftLevel); parent->opCtrl[op].sclLeftDepth->bind(sclLeftLevel);
parent->opCtrl[op].sclRightDepth->bind(sclRightLevel); parent->opCtrl[op].sclRightDepth->bind(sclRightLevel);
parent->opCtrl[op].sclRate->bind(sclRateScaling); parent->opCtrl[op].sclRate->bind(sclRateScaling);
} }
@ -481,32 +481,32 @@ void OperatorEditor::updateGain(float v) {
void OperatorEditor::updateDisplay() { void OperatorEditor::updateDisplay() {
float freq = opCoarse->getValue(); float freq = opCoarse->getValue();
float fine = opFine->getValue(); float fine = opFine->getValue();
String txtFreq; String txtFreq;
if (opMode->getSelectedItemIndex() == 0) { if (opMode->getSelectedItemIndex() == 0) {
if ( freq == 0 ) if ( freq == 0 )
freq = 0.5; freq = 0.5;
txtFreq << "f = " << (freq + ((freq*2) * (fine/100))); txtFreq << "f = " << (freq + ((freq*2) * (fine/100)));
} else { } else {
freq = pow(10,((int)freq)&3); freq = pow(10,((int)freq)&3);
freq = freq + ((freq*10) * (fine/100)); freq = freq + ((freq*10) * (fine/100));
txtFreq << freq << " Hz"; txtFreq << freq << " Hz";
} }
int det = detune->getValue() - 7; int det = detune->getValue() - 7;
if ( det != 0 ) { if ( det != 0 ) {
if ( det > 0 ) if ( det > 0 )
txtFreq << " +" << det; txtFreq << " +" << det;
else else
txtFreq << " " << det; txtFreq << " " << det;
} }
khzDisplay->setText(txtFreq, NotificationType::dontSendNotification); khzDisplay->setText(txtFreq, NotificationType::dontSendNotification);
envDisplay->repaint(); envDisplay->repaint();
} }
void OperatorEditor::updateEnv() { void OperatorEditor::updateEnv() {
//envDisplay->update(s_) //envDisplay->update(s_)
} }
//[/MiscUserCode] //[/MiscUserCode]

@ -31,6 +31,7 @@ DexedAudioProcessorEditor::DexedAudioProcessorEditor (DexedAudioProcessor* owner
: AudioProcessorEditor (ownerFilter), : AudioProcessorEditor (ownerFilter),
midiKeyboard (ownerFilter->keyboardState, MidiKeyboardComponent::horizontalKeyboard) midiKeyboard (ownerFilter->keyboardState, MidiKeyboardComponent::horizontalKeyboard)
{ {
LookAndFeel::setDefaultLookAndFeel(&dx_lnf); LookAndFeel::setDefaultLookAndFeel(&dx_lnf);
// This is where our plugin's editor size is set. // This is where our plugin's editor size is set.
@ -38,12 +39,12 @@ DexedAudioProcessorEditor::DexedAudioProcessorEditor (DexedAudioProcessor* owner
processor = ownerFilter; processor = ownerFilter;
cachedImage_background_png = ImageCache::getFromMemory (BinaryData::background_png, BinaryData::background_pngSize);
addAndMakeVisible (loadButton = new TextButton("LOAD")); addAndMakeVisible (loadButton = new TextButton("LOAD"));
loadButton->setButtonText ("LOAD"); loadButton->setButtonText ("LOAD");
loadButton->addListener (this); loadButton->addListener (this);
loadButton->setBounds (5, 5, 50, 18); loadButton->setBounds(5, 5, 50, 18);
cachedImage_background_png = ImageCache::getFromMemory (BinaryData::background_png, BinaryData::background_pngSize);
addAndMakeVisible( saveButton = new TextButton("SAVE")); addAndMakeVisible( saveButton = new TextButton("SAVE"));
saveButton->setButtonText ("SAVE"); saveButton->setButtonText ("SAVE");
@ -66,12 +67,7 @@ DexedAudioProcessorEditor::DexedAudioProcessorEditor (DexedAudioProcessor* owner
presets.setTextWhenNothingSelected (String::empty); presets.setTextWhenNothingSelected (String::empty);
presets.setBounds(115, 5, 180, 18); presets.setBounds(115, 5, 180, 18);
for(int i=0;i<processor->getNumPrograms();i++) { rebuildPresetCombobox();
String id;
id << (i+1) << ". " << processor->getProgramName(i);
presets.addItem(id, i+1);
}
presets.setSelectedId(processor->getCurrentProgram()+1, NotificationType::dontSendNotification);
presets.addListener(this); presets.addListener(this);
// OPERATORS // OPERATORS
@ -130,7 +126,7 @@ void DexedAudioProcessorEditor::paint (Graphics& g) {
void DexedAudioProcessorEditor::buttonClicked(Button *buttonThatWasClicked) { void DexedAudioProcessorEditor::buttonClicked(Button *buttonThatWasClicked) {
if (buttonThatWasClicked == loadButton) { if (buttonThatWasClicked == loadButton) {
FileChooser fc ("Import original DX sysex...", File::nonexistent, "*.syx;*.SYX", 1); FileChooser fc ("Import original DX sysex...", File::nonexistent, "*.syx;*.SYX;*.*", 1);
if ( fc.browseForFileToOpen()) { if ( fc.browseForFileToOpen()) {
String f = fc.getResults().getReference(0).getFullPathName(); String f = fc.getResults().getReference(0).getFullPathName();
@ -143,14 +139,10 @@ void DexedAudioProcessorEditor::buttonClicked(Button *buttonThatWasClicked) {
return; return;
} }
fp_in.read((char *)syx_data, 4104); fp_in.read((char *)syx_data, 4104);
fp_in.close();
processor->importSysex((char *) &syx_data); processor->importSysex((char *) &syx_data);
presets.clear(NotificationType::dontSendNotification); rebuildPresetCombobox();
for(int i=0;i<processor->getNumPrograms();i++) {
String id;
id << (i+1) << ". " << processor->getProgramName(i);
presets.addItem(id, i+1);
}
presets.setSelectedId(processor->getCurrentProgram()+1, NotificationType::dontSendNotification); presets.setSelectedId(processor->getCurrentProgram()+1, NotificationType::dontSendNotification);
processor->setCurrentProgram(0); processor->setCurrentProgram(0);
@ -160,9 +152,62 @@ void DexedAudioProcessorEditor::buttonClicked(Button *buttonThatWasClicked) {
return; return;
} }
if (buttonThatWasClicked == saveButton) {
FileChooser fc ("Export DX sysex...", File::nonexistent, "*.syx", 1);
if ( fc.browseForFileToSave(true) ) {
String f = fc.getResults().getReference(0).getFullPathName();
uint8_t syx_data[4104];
processor->exportSysex((char *) syx_data);
ofstream fp_out(f.toRawUTF8(), ios::binary);
fp_out.write((char *)syx_data, 4104);
fp_out.close();
if (fp_out.fail()) {
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
"Error",
"Unable to write: " + f);
return;
}
}
return;
}
if (buttonThatWasClicked == storeButton) {
AlertWindow dialog(String("Store Program Destination"), "", AlertWindow::NoIcon, this);
dialog.addTextEditor(String("Name"), processor->getProgramName(processor->getCurrentProgram()), String("Name"), false);
StringArray programs;
for(int i=0;i<32;i++) {
programs.add(presets.getItemText(i));
}
dialog.addComboBox(String("Dest"), programs);
dialog.addButton("OK", 0, KeyPress(KeyPress::returnKey));
dialog.addButton("Cancel", 1, KeyPress(KeyPress::escapeKey));
if ( dialog.runModalLoop() == 0 ) {
TextEditor *name = dialog.getTextEditor(String("Name"));
ComboBox *dest = dialog.getComboBoxComponent(String("Dest"));
int programNum = dest->getSelectedItemIndex();
const char *programName = name->getText().toRawUTF8();
processor->packProgram(programNum, programName);
rebuildPresetCombobox();
processor->setCurrentProgram(programNum);
processor->updateHostDisplay();
}
return;
}
if (buttonThatWasClicked == aboutButton) { if (buttonThatWasClicked == aboutButton) {
AlertWindow::showMessageBoxAsync(AlertWindow::NoIcon, "DEXED - DX Emulator", "(c) 2013 Pascal Gauthier\nUnder the GPL v2" AlertWindow::showMessageBoxAsync(AlertWindow::NoIcon, "DEXED - DX Emulator 0.3", "https://github.com/asb2m10/dexed\n"
"\nBased on Music Synthesizer for Android\n"); "(c) 2013 Pascal Gauthier\nUnder the GPL v2\n\n"
"Based on Music Synthesizer for Android\nhttps://code.google.com/p/music-synthesizer-for-android");
return; return;
} }
@ -206,3 +251,14 @@ void DexedAudioProcessorEditor::updateUI() {
global.repaint(); global.repaint();
} }
void DexedAudioProcessorEditor::rebuildPresetCombobox() {
presets.clear(NotificationType::dontSendNotification);
for(int i=0;i<processor->getNumPrograms();i++) {
String id;
id << (i+1) << ". " << processor->getProgramName(i);
presets.addItem(id, i+1);
}
presets.setSelectedId(processor->getCurrentProgram()+1, NotificationType::dontSendNotification);
}

@ -60,6 +60,8 @@ public:
GlobalEditor global; GlobalEditor global;
void updateUI(); void updateUI();
void rebuildPresetCombobox();
Image cachedImage_background_png; Image cachedImage_background_png;
}; };

@ -36,22 +36,22 @@ static float gaintable[199] = {
static inline float saturate(float input) { //clamp without branching static inline float saturate(float input) { //clamp without branching
#define _limit 0.95 #define _limit 0.95
float x1 = fabsf( input + _limit ); float x1 = fabsf( input + _limit );
float x2 = fabsf( input - _limit ); float x2 = fabsf( input - _limit );
return 0.5 * (x1 - x2); return 0.5 * (x1 - x2);
} }
static inline float crossfade(float amount, float a, float b) { static inline float crossfade(float amount, float a, float b) {
return (1-amount) * a + amount * b; return (1-amount) * a + amount * b;
} }
void PluginFx::init(int sampleRate) { void PluginFx::init(int sampleRate) {
uiCutoff = 1; uiCutoff = 1;
uiReso = 0; uiReso = 0;
srate = sampleRate; srate = sampleRate;
output = 0; output = 0;
for(int i=0;i<4;i++) for(int i=0;i<4;i++)
state[i] = 0; state[i] = 0;
} }
void PluginFx::process(float *work, int sampleSize) { void PluginFx::process(float *work, int sampleSize) {
@ -60,36 +60,36 @@ void PluginFx::process(float *work, int sampleSize) {
if ( uiCutoff == 1 ) if ( uiCutoff == 1 )
return; return;
// the UI values haved changed // the UI values haved changed
if ( uiCutoff != pCutoff || uiReso != pReso) { if ( uiCutoff != pCutoff || uiReso != pReso) {
// calc cutoff // calc cutoff
// mel scale freq : http://www.speech.kth.se/~giampi/auditoryscales/ // mel scale freq : http://www.speech.kth.se/~giampi/auditoryscales/
float freqCutoff = 700 * (pow(M_E,(uiCutoff*4000/1127)-1)) + 20; float freqCutoff = 700 * (pow(M_E,(uiCutoff*4000/1127)-1)) + 20;
float fc = 2 * freqCutoff / srate; float fc = 2 * freqCutoff / srate;
float x2 = fc*fc; float x2 = fc*fc;
float x3 = fc*x2; float x3 = fc*x2;
p = -0.69346 * x3 - 0.59515 * x2 + 3.2937 * fc - 1.0072; //cubic fit p = -0.69346 * x3 - 0.59515 * x2 + 3.2937 * fc - 1.0072; //cubic fit
// calc reso // calc reso
float ix = p * 99; float ix = p * 99;
int ixint = floor( ix ); int ixint = floor( ix );
float ixfrac = ix - ixint; float ixfrac = ix - ixint;
Q = uiReso * crossfade( ixfrac, gaintable[ ixint + 99 ], gaintable[ ixint + 100 ] ); Q = uiReso * crossfade( ixfrac, gaintable[ ixint + 99 ], gaintable[ ixint + 100 ] );
pCutoff = uiCutoff; pCutoff = uiCutoff;
pReso = uiReso; pReso = uiReso;
} }
for (int i=0; i < sampleSize; i++ ) { for (int i=0; i < sampleSize; i++ ) {
output = 0.10 * ( work[i] - output ); //negative feedback output = 0.10 * ( work[i] - output ); //negative feedback
for(int pole=0; pole < 4; pole++) { for(int pole=0; pole < 4; pole++) {
float temp = state[pole]; float temp = state[pole];
output = saturate( output + p * (output - temp)); output = saturate( output + p * (output - temp));
state[pole] = output; state[pole] = output;
output = saturate( output + temp ); output = saturate( output + temp );
} }
work[i] = output; work[i] = output;
output *= Q; //scale the feedback output *= Q; //scale the feedback
} }
} }

@ -87,21 +87,21 @@ void Ctrl::comboBoxChanged(ComboBox* combo) {
// ************************************************************************ // ************************************************************************
// CtrlDX - control DX mapping // CtrlDX - control DX mapping
CtrlFloat::CtrlFloat(String name, float *storageValue) : Ctrl(name) { CtrlFloat::CtrlFloat(String name, float *storageValue) : Ctrl(name) {
vPointer = storageValue; vPointer = storageValue;
} }
float CtrlFloat::getValueHost() { float CtrlFloat::getValueHost() {
return *vPointer; return *vPointer;
} }
void CtrlFloat::setValueHost(float v) { void CtrlFloat::setValueHost(float v) {
*vPointer = v; *vPointer = v;
} }
String CtrlFloat::getValueDisplay() { String CtrlFloat::getValueDisplay() {
String display; String display;
display << *vPointer; display << *vPointer;
return display; return display;
} }
void CtrlFloat::updateComponent() { void CtrlFloat::updateComponent() {
@ -152,11 +152,11 @@ String CtrlDX::getValueDisplay() {
} }
void CtrlDX::publishValue(float value) { void CtrlDX::publishValue(float value) {
Ctrl::publishValue(value / steps); Ctrl::publishValue(value / steps);
DexedAudioProcessorEditor *editor = (DexedAudioProcessorEditor *) parent->getActiveEditor(); DexedAudioProcessorEditor *editor = (DexedAudioProcessorEditor *) parent->getActiveEditor();
if ( editor == NULL ) if ( editor == NULL )
return; return;
String msg; String msg;
msg << label << " = " << getValueDisplay(); msg << label << " = " << getValueDisplay();
editor->global.setParamMessage(msg); editor->global.setParamMessage(msg);
@ -352,7 +352,10 @@ void DexedAudioProcessor::initCtrl() {
} }
int DexedAudioProcessor::importSysex(const char *imported) { int DexedAudioProcessor::importSysex(const char *imported) {
memcpy(sysex, imported + 6, 4104); // reset current program
currentProgram = 0;
memcpy(sysex, imported + 6, 4096);
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
memcpy(patchNames[i], sysex + ((i * 128) + 118), 11); memcpy(patchNames[i], sysex + ((i * 128) + 118), 11);
@ -380,6 +383,26 @@ int DexedAudioProcessor::importSysex(const char *imported) {
return 0; return 0;
} }
void DexedAudioProcessor::exportSysex(char *dest) {
uint8_t header[] = { 0xF0, 0x43, 0x00, 0x09, 0x20, 0x00 };
memcpy(dest, header, 6);
// copy 32 voices
memcpy(dest+6, sysex, 4096);
// make checksum for dump
uint8_t footer[] = { 0x00, 0xF7 };
uint8_t sum = 0;
for (int i=0; i<4096; i++)
sum = (sum + sysex[i]) % (1 << 8);
footer[0] = ((1 << 8) - sum);
memcpy(dest+4102, footer, 2);
}
/*
*/
void DexedAudioProcessor::unpackProgram(int idx) { void DexedAudioProcessor::unpackProgram(int idx) {
char *bulk = sysex + (idx * 128); char *bulk = sysex + (idx * 128);
@ -419,6 +442,42 @@ void DexedAudioProcessor::unpackProgram(int idx) {
data[160] = 1; data[160] = 1;
} }
void DexedAudioProcessor::packProgram(int idx, const char *name) {
char *bulk = sysex + (idx * 128);
for(int op = 0; op < 6; op++) {
// eg rate and level, brk pt, depth, scaling
memcpy(bulk + op * 17, data + op * 21, 11);
int pp = op*17;
int up = op*21;
bulk[pp+11] = (data[up+11]&0x03) | ((data[up+12]&0x03) << 2);
bulk[pp+12] = (data[up+13]&0x07) | ((data[up+20]*0x0f) << 3);
bulk[pp+13] = (data[up+14]&0x03) | ((data[up+15]*0x07) << 2);
bulk[pp+14] = data[up+16];
}
memcpy(bulk + 102, data + 126, 9); // pitch env, algo
bulk[111] = (data[135]&0x07) | ((data[136]&0x01) << 3);
memcpy(bulk + 112, data + 137, 4); // lfo
bulk[116] = (data[141]&0x01) | (((data[142]&0x07) << 1) | ((data[143]&0x07) << 4));
int eos = 0;
for(int i=0; i < 10; i++) {
char c = name[i];
if ( c == 0 )
eos = 1;
if ( eos ) {
bulk[117+i] = ' ';
continue;
}
c = c < 32 ? ' ' : c;
c = c > 127 ? ' ' : c;
bulk[117+i] = c;
}
memcpy(patchNames[idx], bulk+117, 10);
patchNames[idx][10] = 0;
}
void DexedAudioProcessor::updateProgramFromSysex(const uint8 *rawdata) { void DexedAudioProcessor::updateProgramFromSysex(const uint8 *rawdata) {
memcpy(data, rawdata, 160); memcpy(data, rawdata, 160);
refreshUI = true; refreshUI = true;

@ -64,10 +64,6 @@ class DexedAudioProcessor : public AudioProcessor
char sysex[4096]; char sysex[4096];
char patchNames[32][13]; char patchNames[32][13];
void packProgram(int idx);
void unpackProgram(int idx);
void updateProgramFromSysex(const uint8 *rawdata);
/** /**
* PlugFX * PlugFX
*/ */
@ -88,7 +84,6 @@ class DexedAudioProcessor : public AudioProcessor
void initCtrl(); void initCtrl();
public : public :
static const int REFRESH_MSG = 1; static const int REFRESH_MSG = 1;
static const int REFRESH_COMP = 1 << 1; static const int REFRESH_COMP = 1 << 1;
@ -118,6 +113,7 @@ public :
ScopedPointer<CtrlFloat> fxReso; ScopedPointer<CtrlFloat> fxReso;
int importSysex(const char *imported); int importSysex(const char *imported);
void exportSysex(char *dest);
void setDxValue(int offset, int v); void setDxValue(int offset, int v);
//============================================================================== //==============================================================================
@ -134,6 +130,9 @@ public :
bool hasEditor() const; bool hasEditor() const;
void updateUI(); void updateUI();
bool peekEnvStatus(int32_t *values); bool peekEnvStatus(int32_t *values);
void packProgram(int idx, const char *name);
void unpackProgram(int idx);
void updateProgramFromSysex(const uint8 *rawdata);
//============================================================================== //==============================================================================
const String getName() const; const String getName() const;

Loading…
Cancel
Save