diff --git a/Builds/MacOSX/Dexed.xcodeproj/project.xcworkspace/xcuserdata/asb2m10.xcuserdatad/UserInterfaceState.xcuserstate b/Builds/MacOSX/Dexed.xcodeproj/project.xcworkspace/xcuserdata/asb2m10.xcuserdatad/UserInterfaceState.xcuserstate index e76535f..590797f 100644 Binary files a/Builds/MacOSX/Dexed.xcodeproj/project.xcworkspace/xcuserdata/asb2m10.xcuserdatad/UserInterfaceState.xcuserstate and b/Builds/MacOSX/Dexed.xcodeproj/project.xcworkspace/xcuserdata/asb2m10.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Source/OperatorEditor.cpp b/Source/OperatorEditor.cpp index c590ea1..8bcc3fc 100644 --- a/Source/OperatorEditor.cpp +++ b/Source/OperatorEditor.cpp @@ -360,13 +360,13 @@ void OperatorEditor::sliderValueChanged (Slider* sliderThatWasMoved) else if (sliderThatWasMoved == opFine) { //[UserSliderCode_opFine] -- add your slider handling code here.. - updateDisplay(); + updateDisplay(); //[/UserSliderCode_opFine] } else if (sliderThatWasMoved == opCoarse) { //[UserSliderCode_opCoarse] -- add your slider handling code here.. - updateDisplay(); + updateDisplay(); //[/UserSliderCode_opCoarse] } else if (sliderThatWasMoved == gain) @@ -377,7 +377,7 @@ void OperatorEditor::sliderValueChanged (Slider* sliderThatWasMoved) else if (sliderThatWasMoved == detune) { //[UserSliderCode_detune] -- add your slider handling code here.. - updateDisplay(); + updateDisplay(); //[/UserSliderCode_detune] } else if (sliderThatWasMoved == sclLeftLevel) @@ -423,7 +423,7 @@ void OperatorEditor::comboBoxChanged (ComboBox* comboBoxThatHasChanged) if (comboBoxThatHasChanged == opMode) { //[UserComboBoxCode_opMode] -- add your combo box handling code here.. - updateDisplay(); + updateDisplay(); //[/UserComboBoxCode_opMode] } 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... void OperatorEditor::bind(DexedAudioProcessor *parent, int op) { - int targetNum = op+1; + int targetNum = op+1; String opName; opName << "OP" << targetNum; - opId->setText(opName, NotificationType::dontSendNotification); - - parent->opCtrl[op].egLevel[0]->bind(s_egl1); - parent->opCtrl[op].egLevel[1]->bind(s_egl2); - parent->opCtrl[op].egLevel[2]->bind(s_egl3); - parent->opCtrl[op].egLevel[3]->bind(s_egl4); - parent->opCtrl[op].egRate[0]->bind(s_egv1); - parent->opCtrl[op].egRate[1]->bind(s_egv2); - parent->opCtrl[op].egRate[2]->bind(s_egv3); - parent->opCtrl[op].egRate[3]->bind(s_egv4); - parent->opCtrl[op].level->bind(opLevel); - parent->opCtrl[op].opMode->bind(opMode); - parent->opCtrl[op].fine->bind(opFine); - parent->opCtrl[op].coarse->bind(opCoarse); - parent->opCtrl[op].detune->bind(detune); - parent->opCtrl[op].sclBrkPt->bind(sclLvlBrkPt); - parent->opCtrl[op].sclLeftCurve->bind(kbdLeftCurve); - parent->opCtrl[op].sclRightCurve->bind(kbdRightCurve); - parent->opCtrl[op].sclLeftDepth->bind(sclLeftLevel); - parent->opCtrl[op].sclRightDepth->bind(sclRightLevel); + opId->setText(opName, NotificationType::dontSendNotification); + + parent->opCtrl[op].egLevel[0]->bind(s_egl1); + parent->opCtrl[op].egLevel[1]->bind(s_egl2); + parent->opCtrl[op].egLevel[2]->bind(s_egl3); + parent->opCtrl[op].egLevel[3]->bind(s_egl4); + parent->opCtrl[op].egRate[0]->bind(s_egv1); + parent->opCtrl[op].egRate[1]->bind(s_egv2); + parent->opCtrl[op].egRate[2]->bind(s_egv3); + parent->opCtrl[op].egRate[3]->bind(s_egv4); + parent->opCtrl[op].level->bind(opLevel); + parent->opCtrl[op].opMode->bind(opMode); + parent->opCtrl[op].fine->bind(opFine); + parent->opCtrl[op].coarse->bind(opCoarse); + parent->opCtrl[op].detune->bind(detune); + parent->opCtrl[op].sclBrkPt->bind(sclLvlBrkPt); + parent->opCtrl[op].sclLeftCurve->bind(kbdLeftCurve); + parent->opCtrl[op].sclRightCurve->bind(kbdRightCurve); + parent->opCtrl[op].sclLeftDepth->bind(sclLeftLevel); + parent->opCtrl[op].sclRightDepth->bind(sclRightLevel); parent->opCtrl[op].sclRate->bind(sclRateScaling); } @@ -481,32 +481,32 @@ void OperatorEditor::updateGain(float v) { void OperatorEditor::updateDisplay() { float freq = opCoarse->getValue(); float fine = opFine->getValue(); - String txtFreq; - - if (opMode->getSelectedItemIndex() == 0) { - if ( freq == 0 ) - freq = 0.5; - txtFreq << "f = " << (freq + ((freq*2) * (fine/100))); - } else { - freq = pow(10,((int)freq)&3); - freq = freq + ((freq*10) * (fine/100)); - txtFreq << freq << " Hz"; - } - - int det = detune->getValue() - 7; - if ( det != 0 ) { - if ( det > 0 ) - txtFreq << " +" << det; - else - txtFreq << " " << det; - } - khzDisplay->setText(txtFreq, NotificationType::dontSendNotification); - - envDisplay->repaint(); + String txtFreq; + + if (opMode->getSelectedItemIndex() == 0) { + if ( freq == 0 ) + freq = 0.5; + txtFreq << "f = " << (freq + ((freq*2) * (fine/100))); + } else { + freq = pow(10,((int)freq)&3); + freq = freq + ((freq*10) * (fine/100)); + txtFreq << freq << " Hz"; + } + + int det = detune->getValue() - 7; + if ( det != 0 ) { + if ( det > 0 ) + txtFreq << " +" << det; + else + txtFreq << " " << det; + } + khzDisplay->setText(txtFreq, NotificationType::dontSendNotification); + + envDisplay->repaint(); } void OperatorEditor::updateEnv() { - //envDisplay->update(s_) + //envDisplay->update(s_) } //[/MiscUserCode] diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 5c3c841..37370f8 100755 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -31,19 +31,20 @@ DexedAudioProcessorEditor::DexedAudioProcessorEditor (DexedAudioProcessor* owner : AudioProcessorEditor (ownerFilter), midiKeyboard (ownerFilter->keyboardState, MidiKeyboardComponent::horizontalKeyboard) { + LookAndFeel::setDefaultLookAndFeel(&dx_lnf); // This is where our plugin's editor size is set. setSize (865, 420); processor = ownerFilter; - + + cachedImage_background_png = ImageCache::getFromMemory (BinaryData::background_png, BinaryData::background_pngSize); + addAndMakeVisible (loadButton = new TextButton("LOAD")); loadButton->setButtonText ("LOAD"); loadButton->addListener (this); - loadButton->setBounds (5, 5, 50, 18); - - cachedImage_background_png = ImageCache::getFromMemory (BinaryData::background_png, BinaryData::background_pngSize); + loadButton->setBounds(5, 5, 50, 18); addAndMakeVisible( saveButton = new TextButton("SAVE")); saveButton->setButtonText ("SAVE"); @@ -66,12 +67,7 @@ DexedAudioProcessorEditor::DexedAudioProcessorEditor (DexedAudioProcessor* owner presets.setTextWhenNothingSelected (String::empty); presets.setBounds(115, 5, 180, 18); - for(int i=0;igetNumPrograms();i++) { - String id; - id << (i+1) << ". " << processor->getProgramName(i); - presets.addItem(id, i+1); - } - presets.setSelectedId(processor->getCurrentProgram()+1, NotificationType::dontSendNotification); + rebuildPresetCombobox(); presets.addListener(this); // OPERATORS @@ -130,7 +126,7 @@ void DexedAudioProcessorEditor::paint (Graphics& g) { void DexedAudioProcessorEditor::buttonClicked(Button *buttonThatWasClicked) { 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()) { String f = fc.getResults().getReference(0).getFullPathName(); @@ -143,14 +139,10 @@ void DexedAudioProcessorEditor::buttonClicked(Button *buttonThatWasClicked) { return; } fp_in.read((char *)syx_data, 4104); + fp_in.close(); processor->importSysex((char *) &syx_data); - presets.clear(NotificationType::dontSendNotification); - for(int i=0;igetNumPrograms();i++) { - String id; - id << (i+1) << ". " << processor->getProgramName(i); - presets.addItem(id, i+1); - } + rebuildPresetCombobox(); presets.setSelectedId(processor->getCurrentProgram()+1, NotificationType::dontSendNotification); processor->setCurrentProgram(0); @@ -160,9 +152,62 @@ void DexedAudioProcessorEditor::buttonClicked(Button *buttonThatWasClicked) { 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) { - AlertWindow::showMessageBoxAsync(AlertWindow::NoIcon, "DEXED - DX Emulator", "(c) 2013 Pascal Gauthier\nUnder the GPL v2" - "\nBased on Music Synthesizer for Android\n"); + AlertWindow::showMessageBoxAsync(AlertWindow::NoIcon, "DEXED - DX Emulator 0.3", "https://github.com/asb2m10/dexed\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; } @@ -206,3 +251,14 @@ void DexedAudioProcessorEditor::updateUI() { global.repaint(); } + +void DexedAudioProcessorEditor::rebuildPresetCombobox() { + presets.clear(NotificationType::dontSendNotification); + for(int i=0;igetNumPrograms();i++) { + String id; + id << (i+1) << ". " << processor->getProgramName(i); + presets.addItem(id, i+1); + } + presets.setSelectedId(processor->getCurrentProgram()+1, NotificationType::dontSendNotification); +} + diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 4ff801f..0e79462 100755 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -60,6 +60,8 @@ public: GlobalEditor global; void updateUI(); + void rebuildPresetCombobox(); + Image cachedImage_background_png; }; diff --git a/Source/PluginFx.cpp b/Source/PluginFx.cpp index ee52e8a..9403a3d 100644 --- a/Source/PluginFx.cpp +++ b/Source/PluginFx.cpp @@ -10,7 +10,7 @@ ============================================================================== */ -#define _USE_MATH_DEFINES +#define _USE_MATH_DEFINES #include #include "PluginFx.h" #include "PluginProcessor.h" @@ -36,22 +36,22 @@ static float gaintable[199] = { static inline float saturate(float input) { //clamp without branching #define _limit 0.95 - float x1 = fabsf( input + _limit ); - float x2 = fabsf( input - _limit ); - return 0.5 * (x1 - x2); + float x1 = fabsf( input + _limit ); + float x2 = fabsf( input - _limit ); + return 0.5 * (x1 - x2); } 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) { uiCutoff = 1; uiReso = 0; srate = sampleRate; - output = 0; - for(int i=0;i<4;i++) - state[i] = 0; + output = 0; + for(int i=0;i<4;i++) + state[i] = 0; } void PluginFx::process(float *work, int sampleSize) { @@ -60,36 +60,36 @@ void PluginFx::process(float *work, int sampleSize) { if ( uiCutoff == 1 ) return; - // the UI values haved changed - if ( uiCutoff != pCutoff || uiReso != pReso) { - // calc cutoff + // the UI values haved changed + if ( uiCutoff != pCutoff || uiReso != pReso) { + // calc cutoff // mel scale freq : http://www.speech.kth.se/~giampi/auditoryscales/ - float freqCutoff = 700 * (pow(M_E,(uiCutoff*4000/1127)-1)) + 20; - float fc = 2 * freqCutoff / srate; - float x2 = fc*fc; - float x3 = fc*x2; - p = -0.69346 * x3 - 0.59515 * x2 + 3.2937 * fc - 1.0072; //cubic fit + float freqCutoff = 700 * (pow(M_E,(uiCutoff*4000/1127)-1)) + 20; + float fc = 2 * freqCutoff / srate; + float x2 = fc*fc; + float x3 = fc*x2; + p = -0.69346 * x3 - 0.59515 * x2 + 3.2937 * fc - 1.0072; //cubic fit - // calc reso - float ix = p * 99; - int ixint = floor( ix ); - float ixfrac = ix - ixint; - Q = uiReso * crossfade( ixfrac, gaintable[ ixint + 99 ], gaintable[ ixint + 100 ] ); + // calc reso + float ix = p * 99; + int ixint = floor( ix ); + float ixfrac = ix - ixint; + Q = uiReso * crossfade( ixfrac, gaintable[ ixint + 99 ], gaintable[ ixint + 100 ] ); - pCutoff = uiCutoff; - pReso = uiReso; - } + pCutoff = uiCutoff; + pReso = uiReso; + } - for (int i=0; i < sampleSize; i++ ) { - output = 0.10 * ( work[i] - output ); //negative feedback - for(int pole=0; pole < 4; pole++) { - float temp = state[pole]; - output = saturate( output + p * (output - temp)); - state[pole] = output; - output = saturate( output + temp ); - } - work[i] = output; - output *= Q; //scale the feedback - } + for (int i=0; i < sampleSize; i++ ) { + output = 0.10 * ( work[i] - output ); //negative feedback + for(int pole=0; pole < 4; pole++) { + float temp = state[pole]; + output = saturate( output + p * (output - temp)); + state[pole] = output; + output = saturate( output + temp ); + } + work[i] = output; + output *= Q; //scale the feedback + } } diff --git a/Source/PluginParam.cpp b/Source/PluginParam.cpp index bccd199..2839707 100755 --- a/Source/PluginParam.cpp +++ b/Source/PluginParam.cpp @@ -87,21 +87,21 @@ void Ctrl::comboBoxChanged(ComboBox* combo) { // ************************************************************************ // CtrlDX - control DX mapping CtrlFloat::CtrlFloat(String name, float *storageValue) : Ctrl(name) { - vPointer = storageValue; + vPointer = storageValue; } float CtrlFloat::getValueHost() { - return *vPointer; + return *vPointer; } void CtrlFloat::setValueHost(float v) { - *vPointer = v; + *vPointer = v; } String CtrlFloat::getValueDisplay() { - String display; - display << *vPointer; - return display; + String display; + display << *vPointer; + return display; } void CtrlFloat::updateComponent() { @@ -152,11 +152,11 @@ String CtrlDX::getValueDisplay() { } 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 ) - return; + return; String msg; msg << label << " = " << getValueDisplay(); editor->global.setParamMessage(msg); @@ -352,7 +352,10 @@ void DexedAudioProcessor::initCtrl() { } 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++) { memcpy(patchNames[i], sysex + ((i * 128) + 118), 11); @@ -380,6 +383,26 @@ int DexedAudioProcessor::importSysex(const char *imported) { 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) { char *bulk = sysex + (idx * 128); @@ -416,7 +439,43 @@ void DexedAudioProcessor::unpackProgram(int idx) { data[157] = 1; data[158] = 1; data[159] = 1; - 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) { diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index f1316c9..054b546 100755 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -64,10 +64,6 @@ class DexedAudioProcessor : public AudioProcessor char sysex[4096]; char patchNames[32][13]; - void packProgram(int idx); - void unpackProgram(int idx); - void updateProgramFromSysex(const uint8 *rawdata); - /** * PlugFX */ @@ -88,7 +84,6 @@ class DexedAudioProcessor : public AudioProcessor void initCtrl(); - public : static const int REFRESH_MSG = 1; static const int REFRESH_COMP = 1 << 1; @@ -118,6 +113,7 @@ public : ScopedPointer fxReso; int importSysex(const char *imported); + void exportSysex(char *dest); void setDxValue(int offset, int v); //============================================================================== @@ -134,6 +130,9 @@ public : bool hasEditor() const; void updateUI(); 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;