@@ -1,6 +1,6 @@ | |||
/** | |||
* | |||
* Copyright (c) 2014 Pascal Gauthier. | |||
* Copyright (c) 2014, 2017 Pascal Gauthier. | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -27,82 +27,103 @@ AlgoDisplay::AlgoDisplay() { | |||
algo = &tmpAlgo; | |||
} | |||
inline void displayOp(Graphics &g, char id, int x, int y, char link, char fb) { | |||
void AlgoDisplay::displayOp(Graphics &g, char id, int x, int y, char link, char fb) { | |||
const int LINE_SZ = 3; | |||
String t(id); | |||
bool opOn = opStatus[6-id] == '1'; | |||
x *= 25; | |||
x += 3; | |||
y *= 21; | |||
y += 5; | |||
g.setColour(Colours::white); | |||
if ( opOn ) | |||
g.setColour(Colours::white); | |||
else | |||
g.setColour(DXLookNFeel::roundBackground); | |||
g.drawText(t, x, y, 16, 12, Justification::centred, true); | |||
g.setColour(DXLookNFeel::fillColour); | |||
if ( opOn ) | |||
g.setColour(DXLookNFeel::fillColour); | |||
else | |||
g.setColour(DXLookNFeel::roundBackground); | |||
switch(link) { | |||
case 0 : // LINE DOWN | |||
g.drawLine(x+8, y+12, x+8, y+21, LINE_SZ); | |||
break; | |||
case 1: // ARROW TO RIGHT | |||
g.drawLine(x+8, y+12, x+8, y+18, LINE_SZ); | |||
g.drawLine(x+7, y+18, x+34, y+18, LINE_SZ); | |||
break; | |||
case 2: // ARROW TO RIGHT JOIN | |||
g.drawLine(x+8, y+12, x+8, y+19, LINE_SZ); | |||
break; | |||
case 6: | |||
g.drawLine(x+8, y+12, x+8, y+18, LINE_SZ); | |||
g.drawLine(x+7, y+18, x+58, y+18, LINE_SZ); | |||
break; | |||
case 7: // ARROW TO LEFT | |||
g.drawLine(x+8, y+12, x+8, y+19, LINE_SZ); | |||
g.drawLine(x-17, y+18, x+9, y+18, LINE_SZ); | |||
break; | |||
case 0 : // LINE DOWN | |||
g.drawLine(x+8, y+12, x+8, y+21, LINE_SZ); | |||
break; | |||
case 1: // ARROW TO RIGHT | |||
g.drawLine(x+8, y+12, x+8, y+18, LINE_SZ); | |||
g.drawLine(x+7, y+18, x+34, y+18, LINE_SZ); | |||
break; | |||
case 2: // ARROW TO RIGHT JOIN | |||
g.drawLine(x+8, y+12, x+8, y+19, LINE_SZ); | |||
break; | |||
case 3: // ARROW TO RIGHT AND DOWN | |||
g.drawLine(x+8, y+12, x+8, y+21, LINE_SZ); | |||
g.drawLine(x+7, y+18, x+34, y+18, LINE_SZ); | |||
g.drawLine(x+34, y+17, x+34, y+21, LINE_SZ); | |||
break; | |||
case 4: // ARROW TO RIGHT+LEFT AND DOWN | |||
g.drawLine(x+8, y+12, x+8, y+21, LINE_SZ); | |||
g.drawLine(x+7, y+18, x+34, y+18, LINE_SZ); | |||
g.drawLine(x+34, y+17, x+34, y+21, LINE_SZ); | |||
g.drawLine(x-17, y+18, x+8, y+18, LINE_SZ); | |||
g.drawLine(x-17, y+17, x-17, y+21, LINE_SZ); | |||
break; | |||
case 6: | |||
g.drawLine(x+8, y+12, x+8, y+18, LINE_SZ); | |||
g.drawLine(x+7, y+18, x+58, y+18, LINE_SZ); | |||
break; | |||
case 7: // ARROW TO LEFT | |||
g.drawLine(x+8, y+12, x+8, y+19, LINE_SZ); | |||
g.drawLine(x-17, y+18, x+9, y+18, LINE_SZ); | |||
break; | |||
} | |||
switch(fb) { | |||
case 0: | |||
break; | |||
case 1: | |||
g.drawLine(x+7, y, x+8, y-5, LINE_SZ); | |||
g.drawLine(x+8, y-4, x+20, y-4, LINE_SZ); | |||
g.drawLine(x+19, y-4, x+19, y+15, LINE_SZ); | |||
g.drawLine(x+18, y+15, x+19, y+15, LINE_SZ); | |||
g.drawLine(x+8, y+15, x+20, y+15, LINE_SZ); | |||
break; | |||
case 2: // ALGO 4 | |||
g.drawLine(x+7, y, x+8, y-5, LINE_SZ); | |||
g.drawLine(x+8, y-4, x+20, y-4, LINE_SZ); | |||
g.drawLine(x+19, y-4, x+19, y+59, LINE_SZ); | |||
g.drawLine(x+8, y+58, x+19, y+58, LINE_SZ); | |||
break; | |||
case 3: // ALGO 6 | |||
g.drawLine(x+7, y, x+8, y-5, LINE_SZ); | |||
g.drawLine(x+8, y-4, x+20, y-4, LINE_SZ); | |||
g.drawLine(x+19, y-4, x+19, y+37, LINE_SZ); | |||
g.drawLine(x+8, y+36, x+19, y+36, LINE_SZ); | |||
break; | |||
case 4: | |||
g.drawLine(x+7, y, x+8, y-5, LINE_SZ); | |||
g.drawLine(x+8, y-4, x-4, y-4, LINE_SZ); | |||
g.drawLine(x-3, y-4, x-3, y+15, LINE_SZ); | |||
g.drawLine(x-3, y+15, x+8, y+15, LINE_SZ); | |||
g.drawLine(x+8, y+15, x+8, y+12, LINE_SZ); | |||
break; | |||
case 0: | |||
break; | |||
case 1: | |||
g.drawLine(x+7, y, x+8, y-5, LINE_SZ); | |||
g.drawLine(x+8, y-4, x+21, y-4, LINE_SZ); | |||
g.drawLine(x+20, y-4, x+20, y+15, LINE_SZ); | |||
g.drawLine(x+19, y+15, x+20, y+16, LINE_SZ); | |||
g.drawLine(x+8, y+15, x+20, y+15, LINE_SZ); | |||
break; | |||
case 2: // ALGO 4 | |||
g.drawLine(x+7, y, x+8, y-5, LINE_SZ); | |||
g.drawLine(x+8, y-4, x+20, y-4, LINE_SZ); | |||
g.drawLine(x+19, y-4, x+19, y+59, LINE_SZ); | |||
g.drawLine(x+8, y+58, x+19, y+58, LINE_SZ); | |||
break; | |||
case 3: // ALGO 6 | |||
g.drawLine(x+7, y, x+8, y-5, LINE_SZ); | |||
g.drawLine(x+8, y-4, x+20, y-4, LINE_SZ); | |||
g.drawLine(x+19, y-4, x+19, y+37, LINE_SZ); | |||
g.drawLine(x+8, y+36, x+19, y+36, LINE_SZ); | |||
break; | |||
case 4: | |||
g.drawLine(x+7, y, x+8, y-5, LINE_SZ); | |||
g.drawLine(x+8, y-4, x-4, y-4, LINE_SZ); | |||
g.drawLine(x-3, y-4, x-3, y+15, LINE_SZ); | |||
g.drawLine(x-3, y+15, x+8, y+15, LINE_SZ); | |||
g.drawLine(x+8, y+15, x+8, y+12, LINE_SZ); | |||
break; | |||
} | |||
} | |||
void AlgoDisplay::paint(Graphics &g) { | |||
g.setColour(DXLookNFeel::fillColour); | |||
g.fillRect(1, 3, 20, 15); | |||
String n = String(*algo +1); | |||
g.setColour(Colours::white); | |||
g.drawText(n, 1, 3, 20, 15, Justification::centred, true); | |||
switch(*algo) { | |||
case 0: | |||
displayOp(g, 6, 3, 0, 0, 1); | |||
@@ -249,7 +270,7 @@ void AlgoDisplay::paint(Graphics &g) { | |||
displayOp(g, 1, 3, 3, 0, 0); | |||
break; | |||
case 18: | |||
displayOp(g, 6, 3, 2, 0, 1); | |||
displayOp(g, 6, 3, 2, 3, 1); | |||
displayOp(g, 5, 4, 3, 2, 0); | |||
displayOp(g, 4, 3, 3, 1, 0); | |||
displayOp(g, 3, 2, 1, 0, 0); | |||
@@ -260,20 +281,20 @@ void AlgoDisplay::paint(Graphics &g) { | |||
displayOp(g, 6, 4, 2, 0, 0); | |||
displayOp(g, 5, 3, 2, 1, 0); | |||
displayOp(g, 4, 4, 3, 2, 0); | |||
displayOp(g, 3, 1, 2, 0, 1); | |||
displayOp(g, 3, 1, 2, 3, 1); | |||
displayOp(g, 2, 2, 3, 6, 0); | |||
displayOp(g, 1, 1, 3, 1, 0); | |||
break; | |||
case 20: | |||
displayOp(g, 6, 3, 2, 0, 0); | |||
displayOp(g, 6, 3, 2, 3, 0); | |||
displayOp(g, 5, 4, 3, 2, 0); | |||
displayOp(g, 4, 3, 3, 1, 0); | |||
displayOp(g, 3, 1, 2, 0, 1); | |||
displayOp(g, 3, 1, 2, 3, 1); | |||
displayOp(g, 2, 2, 3, 1, 0); | |||
displayOp(g, 1, 1, 3, 1, 0); | |||
break; | |||
case 21: | |||
displayOp(g, 6, 3, 2, 0, 1); | |||
displayOp(g, 6, 3, 2, 4, 1); | |||
displayOp(g, 5, 4, 3, 2, 0); | |||
displayOp(g, 4, 3, 3, 1, 0); | |||
displayOp(g, 3, 2, 3, 1, 0); | |||
@@ -281,7 +302,7 @@ void AlgoDisplay::paint(Graphics &g) { | |||
displayOp(g, 1, 1, 3, 1, 0); | |||
break; | |||
case 22: // CC | |||
displayOp(g, 6, 3, 2, 0, 1); | |||
displayOp(g, 6, 3, 2, 3, 1); | |||
displayOp(g, 5, 4, 3, 2, 0); | |||
displayOp(g, 4, 3, 3, 1, 0); | |||
displayOp(g, 3, 2, 2, 0, 0); | |||
@@ -289,7 +310,7 @@ void AlgoDisplay::paint(Graphics &g) { | |||
displayOp(g, 1, 1, 3, 1, 0); | |||
break; | |||
case 23: // CC | |||
displayOp(g, 6, 3, 2, 0, 1); | |||
displayOp(g, 6, 3, 2, 4, 1); | |||
displayOp(g, 5, 4, 3, 2, 0); | |||
displayOp(g, 4, 3, 3, 1, 0); | |||
displayOp(g, 3, 2, 3, 1, 0); | |||
@@ -297,7 +318,7 @@ void AlgoDisplay::paint(Graphics &g) { | |||
displayOp(g, 1, 0, 3, 1, 0); | |||
break; | |||
case 24: // CC | |||
displayOp(g, 6, 3, 2, 0, 1); | |||
displayOp(g, 6, 3, 2, 3, 1); | |||
displayOp(g, 5, 4, 3, 2, 0); | |||
displayOp(g, 4, 3, 3, 1, 0); | |||
displayOp(g, 3, 2, 3, 1, 0); | |||
@@ -350,7 +371,7 @@ void AlgoDisplay::paint(Graphics &g) { | |||
displayOp(g, 4, 3, 3, 1, 0); | |||
displayOp(g, 3, 2, 3, 1, 0); | |||
displayOp(g, 2, 1, 3, 1, 0); | |||
displayOp(g, 1, 0, 3, 1, 0); | |||
displayOp(g, 1, 0, 3, 1, 0); | |||
break; | |||
case 31: | |||
displayOp(g, 6, 5, 3, 2, 1); | |||
@@ -1,6 +1,6 @@ | |||
/** | |||
* | |||
* Copyright (c) 2014 Pascal Gauthier. | |||
* Copyright (c) 2014-2017 Pascal Gauthier. | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -24,7 +24,9 @@ | |||
#include "JuceHeader.h" | |||
class AlgoDisplay : public Component { | |||
void displayOp(Graphics &g, char id, int x, int y, char link, char fb); | |||
public: | |||
const char *opStatus; | |||
AlgoDisplay(); | |||
char *algo; | |||
void paint(Graphics &g); | |||
@@ -9,6 +9,9 @@ | |||
namespace BinaryData | |||
{ | |||
extern const char* Switch_32x32_png; | |||
const int Switch_32x32_pngSize = 841; | |||
extern const char* Switch_48x26_png; | |||
const int Switch_48x26_pngSize = 2261; | |||
@@ -43,19 +46,19 @@ namespace BinaryData | |||
const int builtin_pgm_zipSize = 88890; | |||
extern const char* about_png; | |||
const int about_pngSize = 23143; | |||
const int about_pngSize = 24863; | |||
extern const char* GlobalEditor_864x144_png; | |||
const int GlobalEditor_864x144_pngSize = 27571; | |||
const int GlobalEditor_864x144_pngSize = 17334; | |||
extern const char* OperatorEditor_287x218_png; | |||
const int OperatorEditor_287x218_pngSize = 15614; | |||
const int OperatorEditor_287x218_pngSize = 10927; | |||
// Points to the start of a list of resource names. | |||
extern const char* namedResourceList[]; | |||
// Number of elements in the namedResourceList array. | |||
const int namedResourceListSize = 14; | |||
const int namedResourceListSize = 15; | |||
// If you provide the name of one of the binary resource variables above, this function will | |||
// return the corresponding data and its size (or a null pointer if the name isn't found). | |||
@@ -1,6 +1,6 @@ | |||
/** | |||
* | |||
* Copyright (c) 2015 Pascal Gauthier. | |||
* Copyright (c) 2015-2017 Pascal Gauthier. | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -31,15 +31,48 @@ class SyxFileFilter : public FileFilter { | |||
public: | |||
SyxFileFilter() : FileFilter(".syx") {} | |||
bool isFileSuitable(const File &file) const { | |||
return file.getFileExtension().toLowerCase() == ".syx" && file.getSize() == 4104; | |||
return file.getFileExtension().toLowerCase() == ".syx" && file.getSize() >= 4096; | |||
} | |||
bool isDirectorySuitable(const File &file) const { | |||
return true; | |||
}; | |||
}; | |||
class FileTreeDrop : public FileTreeComponent { | |||
public : | |||
FileTreeDrop(DirectoryContentsList &listToShow) : FileTreeComponent(listToShow) {} | |||
bool isInterestedInFileDrag (const StringArray &files) override { | |||
bool found = false; | |||
for(int i=0; i<files.size(); i++) { | |||
String filename = files[i].toLowerCase(); | |||
found |= filename.endsWith(".syx"); | |||
} | |||
return found; | |||
} | |||
void filesDropped(const StringArray &files, int x, int y) override { | |||
File targetDir = getSelectedFile(); | |||
if ( ! targetDir.exists() ) | |||
targetDir = DexedAudioProcessor::dexedCartDir; | |||
if ( ! targetDir.isDirectory() ) | |||
targetDir = targetDir.getParentDirectory(); | |||
for(int i=0; i<files.size(); i++) { | |||
if ( files[i].toLowerCase().endsWith(".syx") ) { | |||
File src(files[i]); | |||
File target = targetDir.getChildFile(src.getFileName()); | |||
src.copyFileTo(target); | |||
} | |||
} | |||
refresh(); | |||
} | |||
}; | |||
CartManager::CartManager(DexedAudioProcessorEditor *editor) : Component("CartManager") { | |||
mainWindow = editor; | |||
cartDir = DexedAudioProcessor::dexedCartDir; | |||
@@ -57,8 +90,10 @@ CartManager::CartManager(DexedAudioProcessorEditor *editor) : Component("CartMan | |||
timeSliceThread->startThread(); | |||
cartBrowserList = new DirectoryContentsList(syxFileFilter, *timeSliceThread); | |||
cartBrowserList->setDirectory(cartDir, true, true); | |||
cartBrowser = new FileTreeComponent(*cartBrowserList); | |||
cartBrowser = new FileTreeDrop(*cartBrowserList); | |||
cartBrowser->addKeyListener(this); | |||
addAndMakeVisible(cartBrowser); | |||
cartBrowser->setBounds(23, 18, 590, 384); | |||
cartBrowser->setDragAndDropDescription("Sysex Browser"); | |||
cartBrowser->addListener(this); | |||
@@ -82,7 +117,7 @@ CartManager::CartManager(DexedAudioProcessorEditor *editor) : Component("CartMan | |||
* | |||
* I've removed this since it only works on the DX7 II. TBC. | |||
* | |||
addAndMakeVisible(getDXPgmButton = new TextButton("GET DX7 PGM")); | |||
getDXPgmButton->setBounds(656, 545, 100, 30); | |||
getDXPgmButton->addListener(this); | |||
@@ -90,7 +125,6 @@ CartManager::CartManager(DexedAudioProcessorEditor *editor) : Component("CartMan | |||
addAndMakeVisible(getDXCartButton = new TextButton("GET DX7 CART")); | |||
getDXCartButton->setBounds(755, 545, 100, 30); | |||
getDXCartButton->addListener(this); | |||
*/ | |||
} | |||
@@ -115,10 +149,8 @@ void CartManager::programSelected(ProgramListBox *source, int pos) { | |||
mainWindow->processor->setCurrentProgram(pos); | |||
mainWindow->processor->updateHostDisplay(); | |||
} else { | |||
if ( source->getCurrentCart() == nullptr ) | |||
return; | |||
char unpackPgm[161]; | |||
unpackProgramFromSysex(unpackPgm, source->getCurrentCart(), pos); | |||
uint8_t unpackPgm[161]; | |||
source->getCurrentCart().unpackProgram(unpackPgm, pos); | |||
activeCart->setSelected(-1); | |||
browserCart->setSelected(pos); | |||
repaint(); | |||
@@ -177,11 +209,11 @@ void CartManager::fileDoubleClicked(const File& file) { | |||
if ( file.isDirectory() ) | |||
return; | |||
mainWindow->loadCart(file); | |||
activeCart->setCartridge(mainWindow->processor->sysex); | |||
activeCart->setCartridge(mainWindow->processor->currentCart); | |||
} | |||
void CartManager::fileClicked(const File& file, const MouseEvent& e) { | |||
if ( e.mods.isRightButtonDown() ) { | |||
if ( e.mods.isRightButtonDown() || e.mods.isAnyModifierKeyDown() ) { | |||
PopupMenu menu; | |||
menu.addItem(1000, "Open location"); | |||
@@ -215,7 +247,7 @@ void CartManager::setActiveProgram(int idx, String activeName) { | |||
} | |||
void CartManager::resetActiveSysex() { | |||
activeCart->setCartridge(mainWindow->processor->sysex); | |||
activeCart->setCartridge(mainWindow->processor->currentCart); | |||
} | |||
void CartManager::selectionChanged() { | |||
@@ -227,21 +259,14 @@ void CartManager::selectionChanged() { | |||
if ( file.isDirectory() ) | |||
return; | |||
String f = file.getFullPathName(); | |||
uint8_t syx_data[4104]; | |||
ifstream fp_in(f.toRawUTF8(), ios::binary); | |||
if (fp_in.fail()) { | |||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Error", "Unable to open: " + f); | |||
Cartridge browserSysex; | |||
int rc = browserSysex.load(file); | |||
if ( rc < 0 ) { | |||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Error", "Unable to open file"); | |||
return; | |||
} | |||
fp_in.read((char *)syx_data, 4104); | |||
fp_in.close(); | |||
char browserSysex[4104]; | |||
memcpy(browserSysex, syx_data+6, 4096); | |||
int checksum = sysexChecksum(((char *) &browserSysex), 4096); | |||
if ( checksum != syx_data[4102] ) { | |||
if ( rc != 0 ) { | |||
browserCart->readOnly = true; | |||
} else { | |||
browserCart->readOnly = false; | |||
@@ -260,19 +285,19 @@ void CartManager::programRightClicked(ProgramListBox *source, int pos) { | |||
switch(menu.show()) { | |||
case 1000: | |||
char unpackPgm[161]; | |||
uint8_t unpackPgm[161]; | |||
if ( source == activeCart ) { | |||
unpackProgramFromSysex(unpackPgm, mainWindow->processor->sysex, pos); | |||
mainWindow->processor->currentCart.unpackProgram(unpackPgm, pos); | |||
} else { | |||
char *sysex = source->getCurrentCart(); | |||
if ( sysex == nullptr ) | |||
return; | |||
unpackProgramFromSysex(unpackPgm, sysex, pos); | |||
source->getCurrentCart().unpackProgram(unpackPgm, pos); | |||
} | |||
if ( mainWindow->processor->sysexComm.isOutputActive() ) | |||
mainWindow->processor->sysexComm.send(MidiMessage(unpackPgm, 161)); | |||
if ( mainWindow->processor->sysexComm.isOutputActive() ) { | |||
uint8_t msg[163]; | |||
exportSysexPgm(msg, unpackPgm); | |||
mainWindow->processor->sysexComm.send(MidiMessage(msg, 163)); | |||
} | |||
break; | |||
case 1010: | |||
@@ -284,7 +309,7 @@ void CartManager::programRightClicked(ProgramListBox *source, int pos) { | |||
void CartManager::programDragged(ProgramListBox *destListBox, int dest, char *packedPgm) { | |||
if ( destListBox == activeCart ) { | |||
char *sysex = mainWindow->processor->sysex; | |||
char *sysex = mainWindow->processor->currentCart.getRawVoice(); | |||
memcpy(sysex+(dest*128), packedPgm, 128); | |||
mainWindow->updateUI(); | |||
} else { | |||
@@ -295,22 +320,14 @@ void CartManager::programDragged(ProgramListBox *destListBox, int dest, char *pa | |||
if ( file.isDirectory() ) | |||
return; | |||
if ( file.getSize() > 5000 ) | |||
return; | |||
MemoryBlock block; | |||
file.loadFileAsData(block); | |||
if ( block.getSize() < 4104 ) | |||
if ( file.getSize() != 4104 && file.getSize() != 4096 ) | |||
return; | |||
char *sysex = ((char *) block.getData()) + 6; | |||
memcpy(sysex+(dest*128), packedPgm, 128); | |||
char exported[4104]; | |||
exportSysexCart(exported, sysex, 0); | |||
file.replaceWithData(exported, 4104); | |||
browserCart->setCartridge(sysex); | |||
Cartridge cart; | |||
cart.load(file); | |||
memcpy(cart.getRawVoice()+(dest*128), packedPgm, 128); | |||
cart.saveVoice(file); | |||
browserCart->setCartridge(cart); | |||
} | |||
} | |||
@@ -318,6 +335,18 @@ void CartManager::initialFocus() { | |||
cartBrowser->grabKeyboardFocus(); | |||
} | |||
bool CartManager::keyPressed(const KeyPress& key, Component* originatingComponent) { | |||
if ( key.getKeyCode() == 13 ) { | |||
File file = cartBrowser->getSelectedFile(); | |||
if ( file.isDirectory() ) | |||
return true; | |||
mainWindow->loadCart(file); | |||
activeCart->setCartridge(mainWindow->processor->currentCart); | |||
return true; | |||
} | |||
return false; | |||
} | |||
void CartManager::showSysexConfigMsg() { | |||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Warning", "The DX7 midi interface is not configured correctly.\n\n" | |||
"These buttons are used to 'ask' the DX7 to send the current program/cartridge.\n\n" | |||
@@ -24,9 +24,10 @@ | |||
#include "JuceHeader.h" | |||
#include "PluginData.h" | |||
#include "ProgramListBox.h" | |||
#include "PluginData.h" | |||
class CartManager : public Component, public ButtonListener, public DragAndDropContainer, public FileBrowserListener | |||
, public ProgramListBoxListener { | |||
, public ProgramListBoxListener, public KeyListener { | |||
ScopedPointer<TextButton> newButton; | |||
ScopedPointer<TextButton> loadButton; | |||
ScopedPointer<TextButton> saveButton; | |||
@@ -53,13 +54,13 @@ class CartManager : public Component, public ButtonListener, public DragAndDrop | |||
public: | |||
CartManager(DexedAudioProcessorEditor *editor); | |||
virtual ~CartManager(); | |||
void paint(Graphics& g); | |||
void buttonClicked (Button* buttonThatWasClicked); | |||
void paint(Graphics& g) override; | |||
void buttonClicked (Button* buttonThatWasClicked) override; | |||
void selectionChanged(); | |||
void fileClicked (const File& file, const MouseEvent& e); | |||
void fileDoubleClicked (const File& file); | |||
void browserRootChanged (const File& newRoot); | |||
void selectionChanged() override; | |||
void fileClicked (const File& file, const MouseEvent& e) override; | |||
void fileDoubleClicked (const File& file) override; | |||
void browserRootChanged (const File& newRoot) override; | |||
void setActiveProgram(int idx, String activeName); | |||
void resetActiveSysex(); | |||
@@ -67,6 +68,7 @@ public: | |||
virtual void programSelected(ProgramListBox *source, int pos) override; | |||
virtual void programRightClicked(ProgramListBox *source, int pos) override; | |||
virtual void programDragged(ProgramListBox *destListBox, int dest, char *packedPgm) override; | |||
virtual bool keyPressed(const KeyPress& key, Component* originatingComponent) override; | |||
void initialFocus(); | |||
}; | |||
@@ -1,4 +1,4 @@ | |||
/** | |||
/** | |||
* | |||
* Copyright (c) 2014 Pascal Gauthier. | |||
* | |||
@@ -157,13 +157,13 @@ static double getDuration(int p_rate, int p_level_l, int p_level_r) { | |||
} | |||
EnvDisplay::EnvDisplay() { | |||
pvalues = (char *) &TMP_LEVEL_PTR; | |||
pvalues = (uint8_t *) &TMP_LEVEL_PTR; | |||
} | |||
void EnvDisplay::paint(Graphics &g) { | |||
int h = getHeight(); | |||
char *rates = pvalues; | |||
char *levels = pvalues + 4; | |||
uint8_t *rates = pvalues; | |||
uint8_t *levels = pvalues + 4; | |||
double d[4]; | |||
double keyoff = 0.0; | |||
@@ -250,15 +250,15 @@ void EnvDisplay::paint(Graphics &g) { | |||
} | |||
PitchEnvDisplay::PitchEnvDisplay() { | |||
pvalues = (char *) &TMP_LEVEL_PTR; | |||
pvalues = (uint8_t *) &TMP_LEVEL_PTR; | |||
vPos = 0; | |||
} | |||
void PitchEnvDisplay::paint(Graphics &g) { | |||
g.setColour(Colours::white); | |||
char *levels = pvalues + 4; | |||
char *rates = pvalues; | |||
uint8_t *levels = pvalues + 4; | |||
uint8_t *rates = pvalues; | |||
float dist[4]; | |||
float total = 0; | |||
@@ -363,25 +363,22 @@ ComboBoxImage::ComboBoxImage() { | |||
itemPos[0] = -1; | |||
} | |||
static void comboBoxPopupMenuFinishedCallback (int result, ComboBoxImage* combo) | |||
{ | |||
if (combo != nullptr) | |||
{ | |||
static void comboBoxPopupMenuFinishedCallback (int result, ComboBoxImage* combo) { | |||
if (combo != nullptr) { | |||
combo->hidePopup(); | |||
if (result != 0) | |||
combo->setSelectedId (result); | |||
} | |||
} | |||
void ComboBoxImage::showPopup() { | |||
popup.showMenuAsync (PopupMenu::Options().withTargetComponent (this) | |||
.withItemThatMustBeVisible (getSelectedId()) | |||
.withMinimumWidth (getWidth()) | |||
.withMaximumNumColumns (1) | |||
.withStandardItemHeight (itemHeight), | |||
ModalCallbackFunction::forComponent (comboBoxPopupMenuFinishedCallback, this)); | |||
.withItemThatMustBeVisible(getSelectedId()) | |||
.withMinimumWidth(getWidth()) | |||
.withMaximumNumColumns(1) | |||
.withStandardItemHeight(itemHeight), | |||
ModalCallbackFunction::forComponent(comboBoxPopupMenuFinishedCallback, this)); | |||
} | |||
void ComboBoxImage::setImage(Image image) { | |||
@@ -411,5 +408,35 @@ void ComboBoxImage::setImage(Image image, int pos[]) { | |||
itemPos[i] = pos[i]; | |||
} | |||
void ProgramSelector::mouseDown(const MouseEvent &event) { | |||
if ( event.x < getWidth() - 8) { | |||
ComboBox::mouseDown(event); | |||
return; | |||
} | |||
int cur = getSelectedItemIndex(); | |||
if ( event.y < getHeight() / 2 ) { | |||
if ( cur == 0 ) | |||
cur = 31; | |||
else | |||
cur--; | |||
} else { | |||
if ( cur == 31 ) | |||
cur = 0; | |||
else | |||
cur++; | |||
} | |||
setSelectedItemIndex(cur); | |||
} | |||
void ProgramSelector::paint(Graphics &g) { | |||
int x = getWidth(); | |||
int y = getHeight(); | |||
Path path; | |||
path.addTriangle(x-8, y/2-1, x-4, 2, x, y/2-1); | |||
path.addTriangle(x-8, y/2+1, x-4, y-2, x, y/2+1); | |||
g.setColour(Colours::white); | |||
g.fillPath(path); | |||
} |
@@ -22,11 +22,12 @@ | |||
#define DXCOMPONENTS_H_INCLUDED | |||
#include "JuceHeader.h" | |||
#include <stdint.h> | |||
class EnvDisplay : public Component { | |||
public: | |||
EnvDisplay(); | |||
char *pvalues; | |||
uint8_t *pvalues; | |||
char vPos; | |||
void paint(Graphics &g); | |||
}; | |||
@@ -35,7 +36,7 @@ class PitchEnvDisplay : public Component { | |||
char rvalues[8]; | |||
public: | |||
PitchEnvDisplay(); | |||
char *pvalues; | |||
uint8_t *pvalues; | |||
char vPos; | |||
void paint(Graphics &g); | |||
}; | |||
@@ -58,12 +59,11 @@ class ComboBoxImage : public ComboBox { | |||
Image items; | |||
int itemHeight; | |||
PopupMenu popup; | |||
int itemPos[4]; | |||
public: | |||
ComboBoxImage(); | |||
virtual void paint(Graphics &g); | |||
virtual void paint(Graphics &g) override; | |||
virtual void showPopup() override; | |||
void setImage(Image image); | |||
void setImage(Image image, int pos[]); | |||
@@ -71,6 +71,7 @@ public: | |||
class ProgramSelector : public ComboBox { | |||
public: | |||
void mouseDown(const MouseEvent &event) override; | |||
virtual void paint(Graphics &g) override; | |||
}; | |||
@@ -1,6 +1,6 @@ | |||
/** | |||
* | |||
* Copyright (c) 2013-2014 Pascal Gauthier. | |||
* Copyright (c) 2013-2016 Pascal Gauthier. | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -64,6 +64,7 @@ DXLookNFeel::DXLookNFeel() { | |||
imageKnob = ImageCache::getFromMemory(BinaryData::Knob_34x34_png, BinaryData::Knob_34x34_pngSize); | |||
imageSwitch = ImageCache::getFromMemory(BinaryData::Switch_48x26_png, BinaryData::Switch_48x26_pngSize); | |||
imageSwitchOperator = ImageCache::getFromMemory(BinaryData::Switch_32x32_png, BinaryData::Switch_32x32_pngSize); | |||
imageButton = ImageCache::getFromMemory(BinaryData::ButtonUnlabeled_50x30_png, BinaryData::ButtonUnlabeled_50x30_pngSize); | |||
imageSlider = ImageCache::getFromMemory(BinaryData::Slider_26x26_png, BinaryData::Slider_26x26_pngSize); | |||
imageScaling = ImageCache::getFromMemory(BinaryData::Scaling_36_26_png, BinaryData::Scaling_36_26_pngSize);; | |||
@@ -119,6 +120,10 @@ DXLookNFeel::DXLookNFeel() { | |||
imageSwitch = findImage(path); | |||
continue; | |||
} | |||
if ( name == "Switch_32x64.png" ) { | |||
imageSwitchOperator = findImage(path); | |||
continue; | |||
} | |||
if ( name == "ButtonUnlabeled_50x30.png" ) { | |||
imageButton = findImage(path); | |||
continue; | |||
@@ -1,6 +1,6 @@ | |||
/** | |||
* | |||
* Copyright (c) 2013-2014 Pascal Gauthier. | |||
* Copyright (c) 2013-2016 Pascal Gauthier. | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -35,11 +35,12 @@ public: | |||
Typeface::Ptr defaultFontBold; | |||
Image imageKnob, imageSwitch, imageButton, imageSlider, imageScaling, imageLight, imageLFO; | |||
Image imageSwitchOperator; | |||
Image imageOperator, imageGlobal; | |||
/* overriden methods */ | |||
virtual void drawRotarySlider(Graphics &g, int x, int y, int width, int height, float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, Slider &slider ); | |||
virtual void drawToggleButton(Graphics& g, ToggleButton& button, bool isMouseOverButton, bool isButtonDown); | |||
virtual void drawRotarySlider(Graphics &g, int x, int y, int width, int height, float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, Slider &slider ) override; | |||
virtual void drawToggleButton(Graphics& g, ToggleButton& button, bool isMouseOverButton, bool isButtonDown) override; | |||
virtual void drawLinearSliderBackground (Graphics&, int x, int y, int width, int height, | |||
float sliderPos, float minSliderPos, float maxSliderPos, | |||
const Slider::SliderStyle, Slider&) override; | |||
@@ -49,8 +50,8 @@ public: | |||
virtual void drawButtonBackground (Graphics&, Button&, const Colour& backgroundColour, | |||
bool isMouseOverButton, bool isButtonDown) override; | |||
virtual Font getTextButtonFont(TextButton&, int buttonHeight) override; | |||
virtual Typeface::Ptr getTypefaceForFont(const Font &); | |||
virtual void positionComboBoxText (ComboBox& box, Label& label); | |||
virtual Typeface::Ptr getTypefaceForFont(const Font &) override; | |||
virtual void positionComboBoxText (ComboBox& box, Label& label) override; | |||
static DXLookNFeel *getLookAndFeel(); | |||
static Colour fillColour; | |||
@@ -21,7 +21,20 @@ | |||
#ifndef DEXED_H_INCLUDED | |||
#define DEXED_H_INCLUDED | |||
#define DEXED_VERSION "0.9.0" | |||
#define TRACE(fmt, ...) | |||
void dexed_trace(const char *source, const char *fmt, ...); | |||
#define DEXED_ID "0.9.4" | |||
#ifdef DEBUG | |||
#define DEXED_VERSION DEXED_ID " DEBUG" | |||
#ifdef _MSC_VER | |||
#define TRACE(fmt, ...) dexed_trace(__FUNCTION__,fmt,##__VA_ARGS__) | |||
#else | |||
#define TRACE(fmt, ...) dexed_trace(__PRETTY_FUNCTION__,fmt,##__VA_ARGS__) | |||
#endif | |||
#else | |||
#define DEXED_VERSION DEXED_ID | |||
#define TRACE(fmt, ...) | |||
#endif | |||
#endif // DEXED_H_INCLUDED |
@@ -1,155 +1,211 @@ | |||
/* | |||
* Copyright 2014 Pascal Gauthier. | |||
* Copyright 2012 Google Inc. | |||
* Copyright (C) 2015-2017 Pascal Gauthier. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 3 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |||
* | |||
* The code is based on ppplay https://github.com/stohrendorf/ppplay and opl3 | |||
* math documentation : | |||
* https://github.com/gtaylormb/opl3_fpga/blob/master/docs/opl3math/opl3math.pdf | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
#include "EngineMkI.h" | |||
#include <math.h> | |||
#define _USE_MATH_DEFINES | |||
#include <cmath> | |||
#include <cstdlib> | |||
#include "msfa/sin.h" | |||
#include "msfa/exp2.h" | |||
const FmAlgorithm EngineMkI::algo2[32] = { | |||
{ { 0xc1, 0x11, 0x11, 0x14, 0x01, 0x14 } }, // 1 | |||
{ { 0x01, 0x11, 0x11, 0x14, 0xc1, 0x14 } }, // 2 | |||
{ { 0xc1, 0x11, 0x14, 0x01, 0x11, 0x14 } }, // 3 | |||
{ { 0xc4, 0x00, 0x00, 0x01, 0x11, 0x14 } }, // 4 ** EXCEPTION VIA CODE | |||
{ { 0xc1, 0x14, 0x01, 0x14, 0x01, 0x14 } }, // 5 | |||
{ { 0xc4, 0x00, 0x01, 0x14, 0x01, 0x14 } }, // 6 ** EXCEPTION VIA CODE | |||
{ { 0xc1, 0x11, 0x05, 0x14, 0x01, 0x14 } }, // 7 | |||
{ { 0x01, 0x11, 0xc5, 0x14, 0x01, 0x14 } }, // 8 | |||
{ { 0x01, 0x11, 0x05, 0x14, 0xc1, 0x14 } }, // 9 | |||
{ { 0x01, 0x05, 0x14, 0xc1, 0x11, 0x14 } }, // 10 | |||
{ { 0xc1, 0x05, 0x14, 0x01, 0x11, 0x14 } }, // 11 | |||
{ { 0x01, 0x05, 0x05, 0x14, 0xc1, 0x14 } }, // 12 | |||
{ { 0xc1, 0x05, 0x05, 0x14, 0x01, 0x14 } }, // 13 | |||
{ { 0xc1, 0x05, 0x11, 0x14, 0x01, 0x14 } }, // 14 | |||
{ { 0x01, 0x05, 0x11, 0x14, 0xc1, 0x14 } }, // 15 | |||
{ { 0xc1, 0x11, 0x02, 0x25, 0x05, 0x14 } }, // 16 | |||
{ { 0x01, 0x11, 0x02, 0x25, 0xc5, 0x14 } }, // 17 | |||
{ { 0x01, 0x11, 0x11, 0xc5, 0x05, 0x14 } }, // 18 | |||
{ { 0xc1, 0x14, 0x14, 0x01, 0x11, 0x14 } }, // 19 | |||
{ { 0x01, 0x05, 0x14, 0xc1, 0x14, 0x14 } }, // 20 | |||
{ { 0x01, 0x14, 0x14, 0xc1, 0x14, 0x14 } }, // 21 | |||
{ { 0xc1, 0x14, 0x14, 0x14, 0x01, 0x14 } }, // 22 | |||
{ { 0xc1, 0x14, 0x14, 0x01, 0x14, 0x04 } }, // 23 | |||
{ { 0xc1, 0x14, 0x14, 0x14, 0x04, 0x04 } }, // 24 | |||
{ { 0xc1, 0x14, 0x14, 0x04, 0x04, 0x04 } }, // 25 | |||
{ { 0xc1, 0x05, 0x14, 0x01, 0x14, 0x04 } }, // 26 | |||
{ { 0x01, 0x05, 0x14, 0xc1, 0x14, 0x04 } }, // 27 | |||
{ { 0x04, 0xc1, 0x11, 0x14, 0x01, 0x14 } }, // 28 | |||
{ { 0xc1, 0x14, 0x01, 0x14, 0x04, 0x04 } }, // 29 | |||
{ { 0x04, 0xc1, 0x11, 0x14, 0x04, 0x04 } }, // 30 | |||
{ { 0xc1, 0x14, 0x04, 0x04, 0x04, 0x04 } }, // 31 | |||
{ { 0xc4, 0x04, 0x04, 0x04, 0x04, 0x04 } }, // 32 | |||
}; | |||
#ifdef DEBUG | |||
#include "time.h" | |||
//#define MKIDEBUG | |||
#endif | |||
#ifdef _WIN32 | |||
#if _MSC_VER < 1800 | |||
double log2(double n) { | |||
return log(n) / log(2.0); | |||
} | |||
double round(double n) { | |||
return n < 0.0 ? ceil(n - 0.5) : floor(n + 0.5); | |||
} | |||
#endif | |||
__declspec(align(16)) const int zeros[N] = {0}; | |||
#else | |||
const int32_t __attribute__ ((aligned(16))) zeros[N] = {0}; | |||
#endif | |||
static const uint16_t NEGATIVE_BIT = 0x8000; | |||
static const uint16_t ENV_BITDEPTH = 14; | |||
static const uint16_t SINLOG_BITDEPTH = 10; | |||
static const uint16_t SINLOG_TABLESIZE = 1<<SINLOG_BITDEPTH; | |||
static uint16_t sinLogTable[SINLOG_TABLESIZE]; | |||
static const uint16_t SINEXP_BITDEPTH = 10; | |||
static const uint16_t SINEXP_TABLESIZE = 1<<SINEXP_BITDEPTH; | |||
static uint16_t sinExpTable[SINEXP_TABLESIZE]; | |||
const uint16_t ENV_MAX = 1<<ENV_BITDEPTH; | |||
static inline uint16_t sinLog(uint16_t phi) { | |||
const uint16_t SINLOG_TABLEFILTER = SINLOG_TABLESIZE-1; | |||
const uint16_t index = (phi & SINLOG_TABLEFILTER); | |||
switch( ( phi & (SINLOG_TABLESIZE * 3) ) ) { | |||
case 0: | |||
return sinLogTable[index]; | |||
case SINLOG_TABLESIZE: | |||
return sinLogTable[index ^ SINLOG_TABLEFILTER]; | |||
case SINLOG_TABLESIZE * 2 : | |||
return sinLogTable[index] | NEGATIVE_BIT; | |||
default: | |||
return sinLogTable[index ^ SINLOG_TABLEFILTER] | NEGATIVE_BIT; | |||
} | |||
} | |||
EngineMkI::EngineMkI() { | |||
float bitReso = SINLOG_TABLESIZE; | |||
for(int i=0;i<SINLOG_TABLESIZE;i++) { | |||
float x1 = sin(((0.5+i)/bitReso) * M_PI/2.0); | |||
sinLogTable[i] = round(-1024 * log2(x1)); | |||
} | |||
bitReso = SINEXP_TABLESIZE; | |||
for(int i=0;i<SINEXP_TABLESIZE;i++) { | |||
float x1 = (pow(2, float(i)/bitReso)-1) * 4096; | |||
sinExpTable[i] = round(x1); | |||
} | |||
#ifdef MKIDEBUG | |||
char buffer[4096]; | |||
int pos = 0; | |||
TRACE("****************************************"); | |||
for(int i=0;i<SINLOG_TABLESIZE;i++) { | |||
pos += sprintf(buffer+pos, "%d ", sinLogTable[i]); | |||
if ( pos > 90 ) { | |||
TRACE("SINLOGTABLE: %s" ,buffer); | |||
buffer[0] = 0; | |||
pos = 0; | |||
} | |||
} | |||
TRACE("SINLOGTABLE: %s", buffer); | |||
buffer[0] = 0; | |||
pos = 0; | |||
TRACE("----------------------------------------"); | |||
for(int i=0;i<SINEXP_TABLESIZE;i++) { | |||
pos += sprintf(buffer+pos, "%d ", sinExpTable[i]); | |||
if ( pos > 90 ) { | |||
TRACE("SINEXTTABLE: %s" ,buffer); | |||
buffer[0] = 0; | |||
pos = 0; | |||
} | |||
} | |||
TRACE("SINEXTTABLE: %s", buffer); | |||
TRACE("****************************************"); | |||
#endif | |||
} | |||
inline int32_t mkiSin(int32_t phase, uint16_t env) { | |||
uint16_t expVal = sinLog(phase >> (22 - SINLOG_BITDEPTH)) + (env); | |||
//int16_t expValShow = expVal; | |||
const bool isSigned = expVal & NEGATIVE_BIT; | |||
expVal &= ~NEGATIVE_BIT; | |||
const uint16_t SINEXP_FILTER = 0x3FF; | |||
uint16_t result = 4096 + sinExpTable[( expVal & SINEXP_FILTER ) ^ SINEXP_FILTER]; | |||
//uint16_t resultB4 = result; | |||
result >>= ( expVal >> 10 ); // exp | |||
#ifdef MKIDEBUG | |||
if ( ( time(NULL) % 5 ) == 0 ) { | |||
if ( expValShow < 0 ) { | |||
expValShow = (expValShow + 0x7FFF) * -1; | |||
} | |||
//TRACE(",%d,%d,%d,%d,%d,%d", phase >> (22 - SINLOG_BITDEPTH), env, expValShow, ( expVal & SINEXP_FILTER ) ^ SINEXP_FILTER, resultB4, result); | |||
} | |||
#endif | |||
if( isSigned ) | |||
return (-result - 1) << 13; | |||
else | |||
return result << 13; | |||
} | |||
void EngineMkI::compute(int32_t *output, const int32_t *input, | |||
int32_t phase0, int32_t freq, | |||
int32_t gain1, int32_t gain2, bool add, const Controllers *controllers) { | |||
int32_t gain1, int32_t gain2, bool add) { | |||
int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; | |||
int32_t gain = gain1; | |||
int32_t phase = phase0; | |||
if (add) { | |||
for (int i = 0; i < N; i++) { | |||
gain += dgain; | |||
int32_t y = Sin::lookup(phase + input[i]); | |||
y &= controllers->sinBitFilter; | |||
int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24; | |||
output[i] += y1; | |||
phase += freq; | |||
} | |||
} else { | |||
for (int i = 0; i < N; i++) { | |||
gain += dgain; | |||
int32_t y = Sin::lookup(phase + input[i]); | |||
y &= controllers->sinBitFilter; | |||
int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24; | |||
output[i] = y1; | |||
phase += freq; | |||
} | |||
const int32_t *adder = add ? output : zeros; | |||
for (int i = 0; i < N; i++) { | |||
gain += dgain; | |||
int32_t y = mkiSin((phase+input[i]), gain); | |||
output[i] = y + adder[i]; | |||
phase += freq; | |||
} | |||
} | |||
void EngineMkI::compute_pure(int32_t *output, int32_t phase0, int32_t freq, | |||
int32_t gain1, int32_t gain2, bool add, const Controllers *controllers) { | |||
int32_t gain1, int32_t gain2, bool add) { | |||
int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; | |||
int32_t gain = gain1; | |||
int32_t phase = phase0; | |||
if (add) { | |||
for (int i = 0; i < N; i++) { | |||
gain += dgain; | |||
int32_t y = Sin::lookup(phase); | |||
y &= controllers->sinBitFilter; | |||
int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24; | |||
output[i] += y1; | |||
phase += freq; | |||
} | |||
} else { | |||
for (int i = 0; i < N; i++) { | |||
gain += dgain; | |||
int32_t y = Sin::lookup(phase); | |||
y &= controllers->sinBitFilter; | |||
int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24; | |||
output[i] = y1; | |||
phase += freq; | |||
} | |||
const int32_t *adder = add ? output : zeros; | |||
for (int i = 0; i < N; i++) { | |||
gain += dgain; | |||
int32_t y = mkiSin(phase , gain); | |||
output[i] = y + adder[i]; | |||
phase += freq; | |||
} | |||
} | |||
void EngineMkI::compute_fb(int32_t *output, int32_t phase0, int32_t freq, | |||
int32_t gain1, int32_t gain2, | |||
int32_t *fb_buf, int fb_shift, bool add, const Controllers *controllers) { | |||
int32_t *fb_buf, int fb_shift, bool add) { | |||
int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; | |||
int32_t gain = gain1; | |||
int32_t phase = phase0; | |||
const int32_t *adder = add ? output : zeros; | |||
int32_t y0 = fb_buf[0]; | |||
int32_t y = fb_buf[1]; | |||
if (add) { | |||
for (int i = 0; i < N; i++) { | |||
gain += dgain; | |||
int32_t scaled_fb = (y0 + y) >> (fb_shift + 1); | |||
y0 = y; | |||
y = Sin::lookup(phase + scaled_fb); | |||
y &= controllers->sinBitFilter; | |||
y = ((int64_t)y * (int64_t)gain) >> 24; | |||
output[i] += y; | |||
phase += freq; | |||
} | |||
} else { | |||
for (int i = 0; i < N; i++) { | |||
gain += dgain; | |||
int32_t scaled_fb = (y0 + y) >> (fb_shift + 1); | |||
y0 = y; | |||
y = Sin::lookup(phase + scaled_fb); | |||
y &= controllers->sinBitFilter; | |||
y = ((int64_t)y * (int64_t)gain) >> 24; | |||
output[i] = y; | |||
phase += freq; | |||
} | |||
for (int i = 0; i < N; i++) { | |||
gain += dgain; | |||
int32_t scaled_fb = (y0 + y) >> (fb_shift + 1); | |||
y0 = y; | |||
y = mkiSin((phase+scaled_fb), gain); | |||
output[i] = y + adder[i]; | |||
phase += freq; | |||
} | |||
fb_buf[0] = y0; | |||
fb_buf[1] = y; | |||
} | |||
// exclusively used for ALGO 6 with feedback | |||
void EngineMkI::compute_fb2(int32_t *output, FmOpParams *parms, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift, const Controllers *cont) { | |||
void EngineMkI::compute_fb2(int32_t *output, FmOpParams *parms, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift) { | |||
int32_t dgain[2]; | |||
int32_t gain[2]; | |||
int32_t phase[2]; | |||
@@ -158,39 +214,37 @@ void EngineMkI::compute_fb2(int32_t *output, FmOpParams *parms, int32_t gain01, | |||
phase[0] = parms[0].phase; | |||
phase[1] = parms[1].phase; | |||
parms[1].gain_out = (ENV_MAX-(parms[1].level_in >> (28-ENV_BITDEPTH))); | |||
gain[0] = gain01; | |||
gain[1] = parms[1].gain_out; | |||
gain[1] = parms[1].gain_out == 0 ? (ENV_MAX-1) : parms[1].gain_out; | |||
dgain[0] = (gain02 - gain01 + (N >> 1)) >> LG_N; | |||
dgain[1] = (parms[1].gain_out - (parms[1].gain_out == 0 ? (ENV_MAX-1) : parms[1].gain_out)); | |||
parms[1].gain_out = Exp2::lookup(parms[1].level_in - (14 * (1 << 24))); | |||
dgain[1] = (parms[1].gain_out - gain[1] + (N >> 1)) >> LG_N; | |||
for (int i = 0; i < N; i++) { | |||
int32_t scaled_fb = (y0 + y) >> (fb_shift + 1); | |||
// op 0 | |||
gain[0] += dgain[0]; | |||
int32_t scaled_fb = (y0 + y) >> (fb_shift + 2); // tsk tsk tsk: this needs some tuning | |||
y0 = y; | |||
y = Sin::lookup(phase[0] + scaled_fb); | |||
y &= cont->sinBitFilter; | |||
y = ((int64_t)y * (int64_t)gain[0]) >> 24; | |||
y = mkiSin(phase[0]+scaled_fb, gain[0]); | |||
phase[0] += parms[0].freq; | |||
// op 1 | |||
gain[1] += dgain[1]; | |||
y = Sin::lookup(phase[1] + y); | |||
y &= cont->sinBitFilter; | |||
y = ((int64_t)y * (int64_t)gain[1]) >> 24; | |||
output[i] = y; | |||
y = mkiSin(phase[1]+y, gain[1]); | |||
phase[1] += parms[1].freq; | |||
output[i] = y; | |||
} | |||
fb_buf[0] = y0; | |||
fb_buf[1] = y; | |||
} | |||
// exclusively used for ALGO 4 with feedback | |||
void EngineMkI::compute_fb3(int32_t *output, FmOpParams *parms, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift, const Controllers *cont) { | |||
void EngineMkI::compute_fb3(int32_t *output, FmOpParams *parms, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift) { | |||
int32_t dgain[3]; | |||
int32_t gain[3]; | |||
int32_t phase[3]; | |||
@@ -200,53 +254,55 @@ void EngineMkI::compute_fb3(int32_t *output, FmOpParams *parms, int32_t gain01, | |||
phase[0] = parms[0].phase; | |||
phase[1] = parms[1].phase; | |||
phase[2] = parms[2].phase; | |||
gain[0] = gain01; | |||
gain[1] = parms[1].gain_out; | |||
gain[2] = parms[2].gain_out; | |||
parms[1].gain_out = (ENV_MAX-(parms[1].level_in >> (28-ENV_BITDEPTH))); | |||
parms[2].gain_out = (ENV_MAX-(parms[2].level_in >> (28-ENV_BITDEPTH))); | |||
gain[0] = gain01; | |||
gain[1] = parms[1].gain_out == 0 ? (ENV_MAX-1) : parms[1].gain_out; | |||
gain[2] = parms[2].gain_out == 0 ? (ENV_MAX-1) : parms[2].gain_out; | |||
dgain[0] = (gain02 - gain01 + (N >> 1)) >> LG_N; | |||
dgain[1] = (parms[1].gain_out - (parms[1].gain_out == 0 ? (ENV_MAX-1) : parms[1].gain_out)); | |||
dgain[2] = (parms[2].gain_out - (parms[2].gain_out == 0 ? (ENV_MAX-1) : parms[2].gain_out)); | |||
parms[1].gain_out = Exp2::lookup(parms[1].level_in - (14 * (1 << 24))); | |||
dgain[1] = (parms[1].gain_out - gain[1] + (N >> 1)) >> LG_N; | |||
parms[2].gain_out = Exp2::lookup(parms[2].level_in - (14 * (1 << 24))); | |||
dgain[2] = (parms[1].gain_out - gain[2] + (N >> 1)) >> LG_N; | |||
for (int i = 0; i < N; i++) { | |||
int32_t scaled_fb = (y0 + y) >> (fb_shift + 1); | |||
// op 0 | |||
gain[0] += dgain[0]; | |||
int32_t scaled_fb = (y0 + y) >> (fb_shift + 6); // tsk tsk tsk: this needs some tuning | |||
y0 = y; | |||
y = Sin::lookup(phase[0] + scaled_fb); | |||
y &= cont->sinBitFilter; | |||
y = ((int64_t)y * (int64_t)gain[0]) >> 24; | |||
y = mkiSin(phase[0]+scaled_fb, gain[0]); | |||
phase[0] += parms[0].freq; | |||
// op 1 | |||
gain[1] += dgain[1]; | |||
y = Sin::lookup(phase[1] + y); | |||
y &= cont->sinBitFilter; | |||
y = ((int64_t)y * (int64_t)gain[1]) >> 24; | |||
y = mkiSin(phase[1]+y, gain[1]); | |||
phase[1] += parms[1].freq; | |||
// op 2 | |||
gain[2] += dgain[2]; | |||
y = Sin::lookup(phase[2] + y); | |||
y &= cont->sinBitFilter; | |||
y = ((int64_t)y * (int64_t)gain[2]) >> 24; | |||
y = mkiSin(phase[2]+y, gain[2]); | |||
phase[2] += parms[2].freq; | |||
output[i] = y; | |||
phase[2] += parms[2].freq; | |||
} | |||
fb_buf[0] = y0; | |||
fb_buf[1] = y; | |||
} | |||
void EngineMkI::render(int32_t *output, FmOpParams *params, int algorithm, | |||
int32_t *fb_buf, int feedback_shift, const Controllers *controllers) { | |||
const int kLevelThresh = 1120; | |||
const FmAlgorithm alg = algo2[algorithm]; | |||
void EngineMkI::render(int32_t *output, FmOpParams *params, int algorithm, int32_t *fb_buf, int feedback_shift) { | |||
const int kLevelThresh = ENV_MAX-100; | |||
FmAlgorithm alg = algorithms[algorithm]; | |||
bool has_contents[3] = { true, false, false }; | |||
bool fb_on = feedback_shift < 16; | |||
switch(algorithm) { | |||
case 3 : case 5 : | |||
if ( fb_on ) | |||
alg.ops[0] = 0xc4; | |||
} | |||
for (int op = 0; op < 6; op++) { | |||
int flags = alg.ops[op]; | |||
@@ -255,11 +311,11 @@ void EngineMkI::render(int32_t *output, FmOpParams *params, int algorithm, | |||
int inbus = (flags >> 4) & 3; | |||
int outbus = flags & 3; | |||
int32_t *outptr = (outbus == 0) ? output : buf_[outbus - 1].get(); | |||
int32_t gain1 = param.gain_out; | |||
int32_t gain2 = Exp2::lookup(param.level_in - (14 * (1 << 24))); | |||
int32_t gain1 = param.gain_out == 0 ? (ENV_MAX-1) : param.gain_out; | |||
int32_t gain2 = ENV_MAX-(param.level_in >> (28-ENV_BITDEPTH)); | |||
param.gain_out = gain2; | |||
if (gain1 >= kLevelThresh || gain2 >= kLevelThresh) { | |||
if (gain1 <= kLevelThresh || gain2 <= kLevelThresh) { | |||
if (!has_contents[outbus]) { | |||
add = false; | |||
@@ -267,41 +323,34 @@ void EngineMkI::render(int32_t *output, FmOpParams *params, int algorithm, | |||
if (inbus == 0 || !has_contents[inbus]) { | |||
// PG: this is my 'dirty' implementation of FB for 2 and 3 operators... | |||
// still needs some tuning... | |||
if ((flags & 0xc0) == 0xc0 && feedback_shift < 16) { | |||
if ((flags & 0xc0) == 0xc0 && fb_on) { | |||
switch ( algorithm ) { | |||
// two operator feedback, process exception for ALGO 6 | |||
case 5 : | |||
compute_fb2(outptr, params, gain1, gain2, fb_buf, feedback_shift, controllers); | |||
params[1].phase += params[1].freq << LG_N; // yuk, hack, we already processed op-5 | |||
op++; // ignore next operator; | |||
break; | |||
// three operator feedback, process exception for ALGO 4 | |||
case 3 : | |||
compute_fb3(outptr, params, gain1, gain2, fb_buf, feedback_shift, controllers); | |||
compute_fb3(outptr, params, gain1, gain2, fb_buf, min((feedback_shift+2), 16)); | |||
params[1].phase += params[1].freq << LG_N; // hack, we already processed op-5 - op-4 | |||
params[2].phase += params[2].freq << LG_N; // yuk yuk | |||
op += 2; // ignore the 2 other operators | |||
break; | |||
// two operator feedback, process exception for ALGO 6 | |||
case 5 : | |||
compute_fb2(outptr, params, gain1, gain2, fb_buf, min((feedback_shift+2), 16)); | |||
params[1].phase += params[1].freq << LG_N; // yuk, hack, we already processed op-5 | |||
op++; // ignore next operator; | |||
break; | |||
default: | |||
// one operator feedback, normal proces | |||
//cout << "\t" << op << " fb " << inbus << outbus << add << endl; | |||
compute_fb(outptr, param.phase, param.freq,gain1, gain2, fb_buf, feedback_shift, add, controllers); | |||
compute_fb(outptr, param.phase, param.freq, gain1, gain2, fb_buf, feedback_shift, add); | |||
break; | |||
} | |||
} else { | |||
// cout << op << " pure " << inbus << outbus << add << endl; | |||
compute_pure(outptr, param.phase, param.freq, gain1, gain2, add, controllers); | |||
compute_pure(outptr, param.phase, param.freq, gain1, gain2, add); | |||
} | |||
} else { | |||
// cout << op << " normal " << inbus << outbus << " " << param.freq << add << endl; | |||
compute(outptr, buf_[inbus - 1].get(), | |||
param.phase, param.freq, gain1, gain2, add, controllers); | |||
compute(outptr, buf_[inbus - 1].get(), param.phase, param.freq, gain1, gain2, add); | |||
} | |||
has_contents[outbus] = true; | |||
} else if (!add) { | |||
has_contents[outbus] = false; | |||
} | |||
@@ -26,24 +26,23 @@ | |||
class EngineMkI : public FmCore { | |||
//refacter this when it is working | |||
const static FmAlgorithm algo2[32]; | |||
public: | |||
virtual void render(int32_t *output, FmOpParams *params, int algorithm, | |||
int32_t *fb_buf, int feedback_shift, const Controllers *controllers); | |||
EngineMkI(); | |||
void render(int32_t *output, FmOpParams *params, int algorithm, int32_t *fb_buf, int feedback_shift) override; | |||
void compute(int32_t *output, const int32_t *input, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, | |||
bool add, const Controllers *controllers); | |||
bool add); | |||
void compute_pure(int32_t *output, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, | |||
bool add, const Controllers *controllers); | |||
bool add); | |||
void compute_fb(int32_t *output, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, | |||
int32_t *fb_buf, int fb_gain, bool add, const Controllers *controllers); | |||
int32_t *fb_buf, int fb_gain, bool add); | |||
void compute_fb2(int32_t *output, FmOpParams *params, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift, const Controllers *controllers); | |||
void compute_fb2(int32_t *output, FmOpParams *params, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift); | |||
void compute_fb3(int32_t *output, FmOpParams *params, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift, const Controllers *controllers); | |||
void compute_fb3(int32_t *output, FmOpParams *params, int32_t gain01, int32_t gain02, int32_t *fb_buf, int fb_shift); | |||
}; | |||
@@ -26,7 +26,7 @@ | |||
#include "EngineOpl.h" | |||
#ifdef _WIN32 | |||
__declspec(align(16)) int zeros[N] = {0}; | |||
__declspec(align(16)) const int zeros[N] = {0}; | |||
#else | |||
const int32_t __attribute__ ((aligned(16))) zeros[N] = {0}; | |||
#endif | |||
@@ -71,7 +71,7 @@ uint16_t sinExpTable[256] = { | |||
937, 942, 948, 953, 959, 964, 969, 975, 980, 986, 991, 996, 1002, 1007, 1013, 1018 | |||
}; | |||
inline uint16_t sinLog( uint16_t phi ) { | |||
inline uint16_t sinLog(uint16_t phi) { | |||
const uint8_t index = (phi & 0xff); | |||
switch( ( phi & 0x0300 ) ) { | |||
@@ -169,7 +169,7 @@ void EngineOpl::compute_fb(int32_t *output, int32_t phase0, int32_t freq, | |||
void EngineOpl::render(int32_t *output, FmOpParams *params, int algorithm, | |||
int32_t *fb_buf, int feedback_shift, const Controllers *controllers) { | |||
int32_t *fb_buf, int feedback_shift) { | |||
const int kLevelThresh = 507; // really ???? | |||
const FmAlgorithm alg = algorithms[algorithm]; | |||
bool has_contents[3] = { true, false, false }; | |||
@@ -211,4 +211,16 @@ void EngineOpl::render(int32_t *output, FmOpParams *params, int algorithm, | |||
} | |||
param.phase += param.freq << LG_N; | |||
} | |||
} | |||
} | |||
@@ -31,7 +31,7 @@ | |||
class EngineOpl : public FmCore { | |||
public: | |||
virtual void render(int32_t *output, FmOpParams *params, int algorithm, | |||
int32_t *fb_buf, int feedback_shift, const Controllers *controllers); | |||
int32_t *fb_buf, int feedback_shift) override; | |||
void compute(int32_t *output, const int32_t *input, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, bool add); | |||
void compute_pure(int32_t *output, int32_t phase0, int32_t freq, int32_t gain1, int32_t gain2, bool add); | |||
void compute_fb(int32_t *output, int32_t phase0, int32_t freq, | |||
@@ -7,12 +7,12 @@ | |||
the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded | |||
and re-saved. | |||
Created with Introjucer version: 3.1.0 | |||
Created with Introjucer version: 3.2.0 | |||
------------------------------------------------------------------------------ | |||
The Introjucer is part of the JUCE library - "Jules' Utility Class Extensions" | |||
Copyright 2004-13 by Raw Material Software Ltd. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
============================================================================== | |||
*/ | |||
@@ -59,11 +59,39 @@ public: | |||
} | |||
} | |||
}; | |||
class AboutBox : public DialogWindow { | |||
public: | |||
Image about_png; | |||
AboutBox(Component *parent) : DialogWindow("About", Colour(0xFF000000), true) { | |||
setUsingNativeTitleBar(false); | |||
setAlwaysOnTop(true); | |||
about_png = ImageCache::getFromMemory(BinaryData::about_png, BinaryData::about_pngSize); | |||
setSize(about_png.getWidth(), about_png.getHeight()); | |||
centreAroundComponent (parent, getWidth(), getHeight()); | |||
} | |||
void closeButtonPressed() { | |||
setVisible (false); | |||
} | |||
void paint(Graphics &g) { | |||
g.drawImage (about_png, 0, 0, about_png.getWidth(), about_png.getHeight(), | |||
0, 0, about_png.getWidth(), about_png.getHeight()); | |||
g.setColour(Colour(0xFF000000)); | |||
String ver("Version " DEXED_VERSION " ; build date " __DATE__ ); | |||
g.drawSingleLineText(ver, 18, 130); | |||
} | |||
}; | |||
//[/MiscUserDefs] | |||
//============================================================================== | |||
GlobalEditor::GlobalEditor () | |||
{ | |||
//[Constructor_pre] You can add your own custom stuff here.. | |||
//[/Constructor_pre] | |||
addAndMakeVisible (lfoSpeed = new Slider ("lfoSpeed")); | |||
lfoSpeed->setRange (0, 99, 1); | |||
lfoSpeed->setSliderStyle (Slider::RotaryVerticalDrag); | |||
@@ -224,6 +252,20 @@ GlobalEditor::GlobalEditor () | |||
addAndMakeVisible (programSelector = new ProgramSelector()); | |||
programSelector->setName ("programSelector"); | |||
addAndMakeVisible (aboutButton = new ImageButton ("aboutButton")); | |||
aboutButton->setButtonText (String::empty); | |||
aboutButton->addListener (this); | |||
aboutButton->setImages (false, true, false, | |||
Image(), 1.000f, Colour (0x00000000), | |||
Image(), 1.000f, Colour (0x00000000), | |||
Image(), 1.000f, Colour (0x00000000)); | |||
addAndMakeVisible (tune = new Slider ("tune")); | |||
tune->setRange (0, 1, 0); | |||
tune->setSliderStyle (Slider::RotaryVerticalDrag); | |||
tune->setTextBoxStyle (Slider::NoTextBox, true, 80, 20); | |||
tune->addListener (this); | |||
//[UserPreSize] | |||
//[/UserPreSize] | |||
@@ -284,6 +326,8 @@ GlobalEditor::~GlobalEditor() | |||
monoMode = nullptr; | |||
lfoType = nullptr; | |||
programSelector = nullptr; | |||
aboutButton = nullptr; | |||
tune = nullptr; | |||
//[Destructor]. You can add your own custom destruction code here.. | |||
@@ -306,6 +350,9 @@ void GlobalEditor::paint (Graphics& g) | |||
void GlobalEditor::resized() | |||
{ | |||
//[UserPreResize] Add your own custom resize code here.. | |||
//[/UserPreResize] | |||
lfoSpeed->setBounds (564, 50, 34, 34); | |||
lfoAmDepth->setBounds (686, 50, 34, 34); | |||
lfoPitchDepth->setBounds (646, 50, 34, 34); | |||
@@ -338,6 +385,8 @@ void GlobalEditor::resized() | |||
monoMode->setBounds (249, 65, 48, 26); | |||
lfoType->setBounds (583, 8, 36, 26); | |||
programSelector->setBounds (153, 115, 112, 18); | |||
aboutButton->setBounds (8, 11, 135, 46); | |||
tune->setBounds (190, 9, 34, 34); | |||
//[UserResized] Add your own custom resize handling here.. | |||
//[/UserResized] | |||
} | |||
@@ -450,6 +499,11 @@ void GlobalEditor::sliderValueChanged (Slider* sliderThatWasMoved) | |||
//[UserSliderCode_output] -- add your slider handling code here.. | |||
//[/UserSliderCode_output] | |||
} | |||
else if (sliderThatWasMoved == tune) | |||
{ | |||
//[UserSliderCode_tune] -- add your slider handling code here.. | |||
//[/UserSliderCode_tune] | |||
} | |||
//[UsersliderValueChanged_Post] | |||
//[/UsersliderValueChanged_Post] | |||
@@ -503,6 +557,13 @@ void GlobalEditor::buttonClicked (Button* buttonThatWasClicked) | |||
repaint(); | |||
//[/UserButtonCode_monoMode] | |||
} | |||
else if (buttonThatWasClicked == aboutButton) | |||
{ | |||
//[UserButtonCode_aboutButton] -- add your button handler code here.. | |||
AboutBox about(this->getParentComponent()); | |||
about.runModalLoop(); | |||
//[/UserButtonCode_aboutButton] | |||
} | |||
//[UserbuttonClicked_Post] | |||
//[/UserbuttonClicked_Post] | |||
@@ -536,14 +597,17 @@ void GlobalEditor::bind(DexedAudioProcessorEditor *edit) { | |||
processor->fxCutoff->bind(cutoff); | |||
processor->fxReso->bind(reso); | |||
processor->output->bind(output); | |||
algoDisplay->algo = &(processor->data[134]); | |||
processor->tune->bind(tune); | |||
algoDisplay->algo = (char *) &(processor->data[134]); | |||
pitchEnvDisplay->pvalues = &(processor->data[126]); | |||
algoDisplay->opStatus = processor->controllers.opSwitch; | |||
editor = edit; | |||
midiMonitor = new MidiMonitor(&(processor->sysexComm)); | |||
addAndMakeVisible(midiMonitor); | |||
midiMonitor->setBounds(155, 21, 80, 45); | |||
//addAndMakeVisible(midiMonitor); | |||
//midiMonitor->setBounds(155, 21, 80, 45); | |||
repaint(); | |||
} | |||
@@ -574,6 +638,15 @@ void GlobalEditor::updateVu(float f) { | |||
void GlobalEditor::setMonoState(bool state) { | |||
monoMode->setToggleState(state ? Button::buttonDown : Button::buttonNormal, dontSendNotification); | |||
} | |||
void GlobalEditor::mouseDown(const MouseEvent &e) { | |||
if ( e.mods.isRightButtonDown() || e.mods.isAnyModifierKeyDown() ) { | |||
PopupMenu popup; | |||
popup.addItem(1, "Send current program to DX7"); | |||
if ( popup.show() == 1 ) | |||
processor->sendCurrentSysexProgram(); | |||
} | |||
} | |||
//[/MiscUserCode] | |||
@@ -705,6 +778,16 @@ BEGIN_JUCER_METADATA | |||
<GENERICCOMPONENT name="programSelector" id="990bbcccae72dbe6" memberName="programSelector" | |||
virtualName="" explicitFocusOrder="0" pos="153 115 112 18" class="ProgramSelector" | |||
params=""/> | |||
<IMAGEBUTTON name="aboutButton" id="d195a60b29440aa1" memberName="aboutButton" | |||
virtualName="" explicitFocusOrder="0" pos="8 11 135 46" buttonText="" | |||
connectedEdges="0" needsCallback="1" radioGroupId="0" keepProportions="0" | |||
resourceNormal="" opacityNormal="1" colourNormal="0" resourceOver="" | |||
opacityOver="1" colourOver="0" resourceDown="" opacityDown="1" | |||
colourDown="0"/> | |||
<SLIDER name="tune" id="d22c34aa3363a28a" memberName="tune" virtualName="" | |||
explicitFocusOrder="0" pos="190 9 34 34" min="0" max="1" int="0" | |||
style="RotaryVerticalDrag" textBoxPos="NoTextBox" textBoxEditable="0" | |||
textBoxWidth="80" textBoxHeight="20" skewFactor="1"/> | |||
</JUCER_COMPONENT> | |||
END_JUCER_METADATA | |||
@@ -7,12 +7,12 @@ | |||
the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded | |||
and re-saved. | |||
Created with Introjucer version: 3.1.0 | |||
Created with Introjucer version: 3.2.0 | |||
------------------------------------------------------------------------------ | |||
The Introjucer is part of the JUCE library - "Jules' Utility Class Extensions" | |||
Copyright 2004-13 by Raw Material Software Ltd. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
============================================================================== | |||
*/ | |||
@@ -61,6 +61,8 @@ public: | |||
void setMonoState(bool state); | |||
ProgramSelector *programs; | |||
ScopedPointer<Component> midiMonitor; | |||
void mouseDown(const MouseEvent& e) override; | |||
//[/UserMethods] | |||
void paint (Graphics& g); | |||
@@ -112,6 +114,8 @@ private: | |||
ScopedPointer<ToggleButton> monoMode; | |||
ScopedPointer<ComboBoxImage> lfoType; | |||
ScopedPointer<ProgramSelector> programSelector; | |||
ScopedPointer<ImageButton> aboutButton; | |||
ScopedPointer<Slider> tune; | |||
//============================================================================== | |||
@@ -33,10 +33,10 @@ | |||
#define JucePlugin_ManufacturerEmail "" | |||
#endif | |||
#ifndef JucePlugin_ManufacturerCode | |||
#define JucePlugin_ManufacturerCode 'DGSB' | |||
#define JucePlugin_ManufacturerCode 0x44475342 // 'DGSB' | |||
#endif | |||
#ifndef JucePlugin_PluginCode | |||
#define JucePlugin_PluginCode 'Dexd' | |||
#define JucePlugin_PluginCode 0x44657864 // 'Dexd' | |||
#endif | |||
#ifndef JucePlugin_MaxNumInputChannels | |||
#define JucePlugin_MaxNumInputChannels 0 | |||
@@ -7,12 +7,12 @@ | |||
the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded | |||
and re-saved. | |||
Created with Introjucer version: 3.1.0 | |||
Created with Introjucer version: 3.2.0 | |||
------------------------------------------------------------------------------ | |||
The Introjucer is part of the JUCE library - "Jules' Utility Class Extensions" | |||
Copyright 2004-13 by Raw Material Software Ltd. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
============================================================================== | |||
*/ | |||
@@ -25,13 +25,30 @@ | |||
//[MiscUserDefs] You can add your own user definitions and misc code here... | |||
#ifndef M_LN10 | |||
#define M_LN10 2.30258509299404568402 | |||
#define M_LN10 2.30258509299404568402 | |||
#endif | |||
class OperatorSwitch : public ToggleButton { | |||
Image image; | |||
public : | |||
OperatorSwitch() : ToggleButton("opSwitch") { | |||
image = DXLookNFeel::getLookAndFeel()->imageSwitchOperator; | |||
setSize(32, 32); | |||
} | |||
void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) { | |||
g.drawImage(image, 0, 0, 32, 32, 0, getToggleState() ? 0 : 32, 32, 32); | |||
} | |||
}; | |||
//[/MiscUserDefs] | |||
//============================================================================== | |||
OperatorEditor::OperatorEditor () | |||
{ | |||
//[Constructor_pre] You can add your own custom stuff here.. | |||
//[/Constructor_pre] | |||
addAndMakeVisible (s_egl1 = new Slider ("egl1")); | |||
s_egl1->setRange (0, 99, 1); | |||
s_egl1->setSliderStyle (Slider::RotaryVerticalDrag); | |||
@@ -173,6 +190,7 @@ OperatorEditor::OperatorEditor () | |||
//[UserPreSize] | |||
addAndMakeVisible(opSwitch = new OperatorSwitch()); | |||
//[/UserPreSize] | |||
setSize (287, 218); | |||
@@ -233,6 +251,7 @@ OperatorEditor::~OperatorEditor() | |||
//[Destructor]. You can add your own custom destruction code here.. | |||
opSwitch = nullptr; | |||
//[/Destructor] | |||
} | |||
@@ -244,9 +263,14 @@ void OperatorEditor::paint (Graphics& g) | |||
//[/UserPrePaint] | |||
//[UserPaint] Add your own custom painting code here.. | |||
g.setColour (Colours::white); | |||
if ( opSwitch->getToggleState() ) | |||
g.setColour(Colours::white); | |||
else | |||
g.setColour(DXLookNFeel::roundBackground); | |||
g.setFont(Font (30.00f, Font::plain)); | |||
g.drawText(opNum, 242, 8, 30, 30, Justification::centred, true); | |||
g.drawText(opNum, 250, 14, 30, 30, Justification::centred, true); | |||
bool state = opMode->getToggleState(); | |||
@@ -259,6 +283,9 @@ void OperatorEditor::paint (Graphics& g) | |||
void OperatorEditor::resized() | |||
{ | |||
//[UserPreResize] Add your own custom resize code here.. | |||
//[/UserPreResize] | |||
s_egl1->setBounds (5, 128, 34, 34); | |||
s_egl2->setBounds (33, 129, 34, 34); | |||
s_egl3->setBounds (61, 128, 34, 34); | |||
@@ -284,6 +311,7 @@ void OperatorEditor::resized() | |||
kbdLeftCurve->setBounds (128, 170, 36, 26); | |||
kbdRightCurve->setBounds (240, 170, 36, 26); | |||
//[UserResized] Add your own custom resize handling here.. | |||
opSwitch->setBounds(226, 13, 64, 32); | |||
//[/UserResized] | |||
} | |||
@@ -439,6 +467,7 @@ void OperatorEditor::bind(DexedAudioProcessor *parent, int op) { | |||
parent->opCtrl[op].sclRate->bind(sclRateScaling); | |||
parent->opCtrl[op].ampModSens->bind(ampModSens); | |||
parent->opCtrl[op].velModSens->bind(keyVelSens); | |||
parent->opCtrl[op].opSwitch->bind(opSwitch); | |||
int offset = parent->opCtrl[op].egRate[0]->getOffset(); | |||
envDisplay->pvalues = &(parent->data[offset]); | |||
@@ -486,7 +515,7 @@ void OperatorEditor::updateEnvPos(char pos) { | |||
} | |||
void OperatorEditor::mouseDown(const MouseEvent &event) { | |||
if ( event.mods.isRightButtonDown() ) { | |||
if ( event.mods.isRightButtonDown() || event.mods.isAnyModifierKeyDown()) { | |||
PopupMenu popup; | |||
popup.addItem(1, "Copy Operator Values"); | |||
@@ -507,7 +536,7 @@ void OperatorEditor::mouseDown(const MouseEvent &event) { | |||
case 3: | |||
processor->pasteOpFromClipboard(internalOp); | |||
break; | |||
case 4: | |||
processor->sendCurrentSysexProgram(); | |||
break; | |||
@@ -7,12 +7,12 @@ | |||
the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded | |||
and re-saved. | |||
Created with Introjucer version: 3.1.0 | |||
Created with Introjucer version: 3.2.0 | |||
------------------------------------------------------------------------------ | |||
The Introjucer is part of the JUCE library - "Jules' Utility Class Extensions" | |||
Copyright 2004-13 by Raw Material Software Ltd. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
============================================================================== | |||
*/ | |||
@@ -56,10 +56,10 @@ public: | |||
void mouseDown(const MouseEvent& e) override; | |||
//[/UserMethods] | |||
void paint (Graphics& g); | |||
void resized(); | |||
void sliderValueChanged (Slider* sliderThatWasMoved); | |||
void buttonClicked (Button* buttonThatWasClicked); | |||
void paint (Graphics& g) override; | |||
void resized() override; | |||
void sliderValueChanged (Slider* sliderThatWasMoved) override; | |||
void buttonClicked (Button* buttonThatWasClicked) override; | |||
@@ -68,10 +68,9 @@ private: | |||
String opNum; | |||
int internalOp; | |||
Image light; | |||
DexedAudioProcessor *processor; | |||
Image background; | |||
ScopedPointer<ToggleButton> opSwitch; | |||
//[/UserVariables] | |||
//============================================================================== | |||
@@ -7,12 +7,12 @@ | |||
the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded | |||
and re-saved. | |||
Created with Introjucer version: 3.1.0 | |||
Created with Introjucer version: 3.2.0 | |||
------------------------------------------------------------------------------ | |||
The Introjucer is part of the JUCE library - "Jules' Utility Class Extensions" | |||
Copyright 2004-13 by Raw Material Software Ltd. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
============================================================================== | |||
*/ | |||
@@ -30,6 +30,9 @@ | |||
//============================================================================== | |||
ParamDialog::ParamDialog () | |||
{ | |||
//[Constructor_pre] You can add your own custom stuff here.. | |||
//[/Constructor_pre] | |||
addAndMakeVisible (pitchRange = new Slider ("pitchRange")); | |||
pitchRange->setRange (0, 12, 1); | |||
pitchRange->setSliderStyle (Slider::RotaryVerticalDrag); | |||
@@ -67,7 +70,7 @@ ParamDialog::ParamDialog () | |||
engineReso->setJustificationType (Justification::centredLeft); | |||
engineReso->setTextWhenNothingSelected (String::empty); | |||
engineReso->setTextWhenNoChoicesAvailable (TRANS("(no choices)")); | |||
engineReso->addItem (TRANS("Modern (Direct)"), 1); | |||
engineReso->addItem (TRANS("Modern (24-bit)"), 1); | |||
engineReso->addItem (TRANS("Mark I"), 2); | |||
engineReso->addItem (TRANS("OPL Series"), 3); | |||
engineReso->addListener (this); | |||
@@ -75,11 +78,83 @@ ParamDialog::ParamDialog () | |||
addAndMakeVisible (showKeyboard = new ToggleButton ("showKeyboard")); | |||
showKeyboard->setButtonText (String::empty); | |||
addAndMakeVisible (whlRange = new Slider ("whlRange")); | |||
whlRange->setRange (0, 99, 1); | |||
whlRange->setSliderStyle (Slider::RotaryVerticalDrag); | |||
whlRange->setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20); | |||
whlRange->addListener (this); | |||
addAndMakeVisible (ftRange = new Slider ("ftRange")); | |||
ftRange->setRange (0, 99, 1); | |||
ftRange->setSliderStyle (Slider::RotaryVerticalDrag); | |||
ftRange->setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20); | |||
ftRange->addListener (this); | |||
addAndMakeVisible (brRange = new Slider ("brRange")); | |||
brRange->setRange (0, 99, 1); | |||
brRange->setSliderStyle (Slider::RotaryVerticalDrag); | |||
brRange->setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20); | |||
brRange->addListener (this); | |||
addAndMakeVisible (atRange = new Slider ("atRange")); | |||
atRange->setRange (0, 99, 1); | |||
atRange->setSliderStyle (Slider::RotaryVerticalDrag); | |||
atRange->setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20); | |||
atRange->addListener (this); | |||
addAndMakeVisible (whlEg = new ToggleButton ("whlEg")); | |||
whlEg->setButtonText (String::empty); | |||
whlEg->addListener (this); | |||
addAndMakeVisible (ftEg = new ToggleButton ("ftEg")); | |||
ftEg->setButtonText (String::empty); | |||
ftEg->addListener (this); | |||
addAndMakeVisible (brEg = new ToggleButton ("brEg")); | |||
brEg->setButtonText (String::empty); | |||
brEg->addListener (this); | |||
addAndMakeVisible (atEg = new ToggleButton ("atEg")); | |||
atEg->setButtonText (String::empty); | |||
atEg->addListener (this); | |||
addAndMakeVisible (whlAmp = new ToggleButton ("whlAmp")); | |||
whlAmp->setButtonText (String::empty); | |||
whlAmp->addListener (this); | |||
addAndMakeVisible (ftAmp = new ToggleButton ("ftAmp")); | |||
ftAmp->setButtonText (String::empty); | |||
ftAmp->addListener (this); | |||
addAndMakeVisible (brAmp = new ToggleButton ("brAmp")); | |||
brAmp->setButtonText (String::empty); | |||
brAmp->addListener (this); | |||
addAndMakeVisible (atAmp = new ToggleButton ("atAmp")); | |||
atAmp->setButtonText (String::empty); | |||
atAmp->addListener (this); | |||
addAndMakeVisible (whlPitch = new ToggleButton ("whlPitch")); | |||
whlPitch->setButtonText (String::empty); | |||
whlPitch->addListener (this); | |||
addAndMakeVisible (ftPitch = new ToggleButton ("ftPitch")); | |||
ftPitch->setButtonText (String::empty); | |||
ftPitch->addListener (this); | |||
addAndMakeVisible (brPitch = new ToggleButton ("brPitch")); | |||
brPitch->setButtonText (String::empty); | |||
brPitch->addListener (this); | |||
addAndMakeVisible (atPitch = new ToggleButton ("atPitch")); | |||
atPitch->setButtonText (String::empty); | |||
atPitch->addListener (this); | |||
//[UserPreSize] | |||
//[/UserPreSize] | |||
setSize (350, 350); | |||
setSize (710, 350); | |||
//[Constructor] You can add your own custom stuff here.. | |||
@@ -110,6 +185,22 @@ ParamDialog::~ParamDialog() | |||
sysexChl = nullptr; | |||
engineReso = nullptr; | |||
showKeyboard = nullptr; | |||
whlRange = nullptr; | |||
ftRange = nullptr; | |||
brRange = nullptr; | |||
atRange = nullptr; | |||
whlEg = nullptr; | |||
ftEg = nullptr; | |||
brEg = nullptr; | |||
atEg = nullptr; | |||
whlAmp = nullptr; | |||
ftAmp = nullptr; | |||
brAmp = nullptr; | |||
atAmp = nullptr; | |||
whlPitch = nullptr; | |||
ftPitch = nullptr; | |||
brPitch = nullptr; | |||
atPitch = nullptr; | |||
//[Destructor]. You can add your own custom destruction code here.. | |||
@@ -172,12 +263,60 @@ void ParamDialog::paint (Graphics& g) | |||
20, 96, 276, 23, | |||
Justification::centredLeft, true); | |||
g.setColour (Colours::black); | |||
g.fillRect (352, 11, 1, 325); | |||
g.setColour (Colours::white); | |||
g.setFont (Font (15.00f, Font::plain)); | |||
g.drawText (TRANS("Wheel"), | |||
368, 16, 276, 23, | |||
Justification::centredLeft, true); | |||
g.setColour (Colours::white); | |||
g.setFont (Font (15.00f, Font::plain)); | |||
g.drawText (TRANS("Foot"), | |||
368, 96, 276, 23, | |||
Justification::centredLeft, true); | |||
g.setColour (Colours::white); | |||
g.setFont (Font (15.00f, Font::plain)); | |||
g.drawText (TRANS("Breath"), | |||
368, 56, 276, 23, | |||
Justification::centredLeft, true); | |||
g.setColour (Colours::white); | |||
g.setFont (Font (15.00f, Font::plain)); | |||
g.drawText (TRANS("After Touch"), | |||
368, 136, 276, 23, | |||
Justification::centredLeft, true); | |||
g.setColour (Colours::white); | |||
g.setFont (Font (15.00f, Font::plain)); | |||
g.drawText (TRANS("PITCH"), | |||
533, 163, 48, 23, | |||
Justification::centredLeft, true); | |||
g.setColour (Colours::white); | |||
g.setFont (Font (15.00f, Font::plain)); | |||
g.drawText (TRANS("AMP"), | |||
589, 163, 48, 23, | |||
Justification::centredLeft, true); | |||
g.setColour (Colours::white); | |||
g.setFont (Font (15.00f, Font::plain)); | |||
g.drawText (TRANS("EG BIAS"), | |||
645, 163, 48, 23, | |||
Justification::centredLeft, true); | |||
//[UserPaint] Add your own custom painting code here.. | |||
//[/UserPaint] | |||
} | |||
void ParamDialog::resized() | |||
{ | |||
//[UserPreResize] Add your own custom resize code here.. | |||
//[/UserPreResize] | |||
pitchRange->setBounds (264, 16, 72, 24); | |||
pitchStep->setBounds (264, 56, 72, 24); | |||
sysexIn->setBounds (104, 224, 224, 24); | |||
@@ -185,6 +324,22 @@ void ParamDialog::resized() | |||
sysexChl->setBounds (264, 304, 72, 24); | |||
engineReso->setBounds (160, 156, 168, 24); | |||
showKeyboard->setBounds (264, 96, 56, 24); | |||
whlRange->setBounds (448, 16, 72, 24); | |||
ftRange->setBounds (448, 56, 72, 24); | |||
brRange->setBounds (448, 96, 72, 24); | |||
atRange->setBounds (448, 136, 72, 24); | |||
whlEg->setBounds (640, 16, 56, 24); | |||
ftEg->setBounds (640, 56, 56, 24); | |||
brEg->setBounds (640, 96, 56, 24); | |||
atEg->setBounds (640, 136, 56, 24); | |||
whlAmp->setBounds (584, 16, 56, 24); | |||
ftAmp->setBounds (584, 56, 56, 24); | |||
brAmp->setBounds (584, 96, 56, 24); | |||
atAmp->setBounds (584, 136, 56, 24); | |||
whlPitch->setBounds (528, 16, 56, 24); | |||
ftPitch->setBounds (528, 56, 56, 24); | |||
brPitch->setBounds (528, 96, 56, 24); | |||
atPitch->setBounds (528, 136, 56, 24); | |||
//[UserResized] Add your own custom resize handling here.. | |||
//[/UserResized] | |||
} | |||
@@ -210,6 +365,26 @@ void ParamDialog::sliderValueChanged (Slider* sliderThatWasMoved) | |||
//[UserSliderCode_sysexChl] -- add your slider handling code here.. | |||
//[/UserSliderCode_sysexChl] | |||
} | |||
else if (sliderThatWasMoved == whlRange) | |||
{ | |||
//[UserSliderCode_whlRange] -- add your slider handling code here.. | |||
//[/UserSliderCode_whlRange] | |||
} | |||
else if (sliderThatWasMoved == ftRange) | |||
{ | |||
//[UserSliderCode_ftRange] -- add your slider handling code here.. | |||
//[/UserSliderCode_ftRange] | |||
} | |||
else if (sliderThatWasMoved == brRange) | |||
{ | |||
//[UserSliderCode_brRange] -- add your slider handling code here.. | |||
//[/UserSliderCode_brRange] | |||
} | |||
else if (sliderThatWasMoved == atRange) | |||
{ | |||
//[UserSliderCode_atRange] -- add your slider handling code here.. | |||
//[/UserSliderCode_atRange] | |||
} | |||
//[UsersliderValueChanged_Post] | |||
//[/UsersliderValueChanged_Post] | |||
@@ -240,6 +415,76 @@ void ParamDialog::comboBoxChanged (ComboBox* comboBoxThatHasChanged) | |||
//[/UsercomboBoxChanged_Post] | |||
} | |||
void ParamDialog::buttonClicked (Button* buttonThatWasClicked) | |||
{ | |||
//[UserbuttonClicked_Pre] | |||
//[/UserbuttonClicked_Pre] | |||
if (buttonThatWasClicked == whlEg) | |||
{ | |||
//[UserButtonCode_whlEg] -- add your button handler code here.. | |||
//[/UserButtonCode_whlEg] | |||
} | |||
else if (buttonThatWasClicked == ftEg) | |||
{ | |||
//[UserButtonCode_ftEg] -- add your button handler code here.. | |||
//[/UserButtonCode_ftEg] | |||
} | |||
else if (buttonThatWasClicked == brEg) | |||
{ | |||
//[UserButtonCode_brEg] -- add your button handler code here.. | |||
//[/UserButtonCode_brEg] | |||
} | |||
else if (buttonThatWasClicked == atEg) | |||
{ | |||
//[UserButtonCode_atEg] -- add your button handler code here.. | |||
//[/UserButtonCode_atEg] | |||
} | |||
else if (buttonThatWasClicked == whlAmp) | |||
{ | |||
//[UserButtonCode_whlAmp] -- add your button handler code here.. | |||
//[/UserButtonCode_whlAmp] | |||
} | |||
else if (buttonThatWasClicked == ftAmp) | |||
{ | |||
//[UserButtonCode_ftAmp] -- add your button handler code here.. | |||
//[/UserButtonCode_ftAmp] | |||
} | |||
else if (buttonThatWasClicked == brAmp) | |||
{ | |||
//[UserButtonCode_brAmp] -- add your button handler code here.. | |||
//[/UserButtonCode_brAmp] | |||
} | |||
else if (buttonThatWasClicked == atAmp) | |||
{ | |||
//[UserButtonCode_atAmp] -- add your button handler code here.. | |||
//[/UserButtonCode_atAmp] | |||
} | |||
else if (buttonThatWasClicked == whlPitch) | |||
{ | |||
//[UserButtonCode_whlPitch] -- add your button handler code here.. | |||
//[/UserButtonCode_whlPitch] | |||
} | |||
else if (buttonThatWasClicked == ftPitch) | |||
{ | |||
//[UserButtonCode_ftPitch] -- add your button handler code here.. | |||
//[/UserButtonCode_ftPitch] | |||
} | |||
else if (buttonThatWasClicked == brPitch) | |||
{ | |||
//[UserButtonCode_brPitch] -- add your button handler code here.. | |||
//[/UserButtonCode_brPitch] | |||
} | |||
else if (buttonThatWasClicked == atPitch) | |||
{ | |||
//[UserButtonCode_atPitch] -- add your button handler code here.. | |||
//[/UserButtonCode_atPitch] | |||
} | |||
//[UserbuttonClicked_Post] | |||
//[/UserbuttonClicked_Post] | |||
} | |||
//[MiscUserCode] You can add your own definitions of your custom methods or any other code here... | |||
@@ -249,6 +494,27 @@ void ParamDialog::setDialogValues(Controllers &c, SysexComm &mgr, int reso, bool | |||
pitchStep->setValue(c.values_[kControllerPitchStep]); | |||
sysexChl->setValue(mgr.getChl() + 1); | |||
whlRange->setValue(c.wheel.range); | |||
whlPitch->setToggleState(c.wheel.pitch, dontSendNotification); | |||
whlPitch->setToggleState(c.wheel.pitch, dontSendNotification); | |||
whlAmp->setToggleState(c.wheel.amp, dontSendNotification); | |||
whlEg->setToggleState(c.wheel.eg, dontSendNotification); | |||
ftRange->setValue(c.foot.range); | |||
ftPitch->setToggleState(c.foot.pitch, dontSendNotification); | |||
ftAmp->setToggleState(c.foot.amp, dontSendNotification); | |||
ftEg->setToggleState(c.foot.eg, dontSendNotification); | |||
brRange->setValue(c.breath.range); | |||
brPitch->setToggleState(c.breath.pitch, dontSendNotification); | |||
brAmp->setToggleState(c.breath.amp, dontSendNotification); | |||
brEg->setToggleState(c.breath.eg, dontSendNotification); | |||
atRange->setValue(c.at.range); | |||
atPitch->setToggleState(c.at.pitch, dontSendNotification); | |||
atAmp->setToggleState(c.at.amp, dontSendNotification); | |||
atEg->setToggleState(c.at.eg, dontSendNotification); | |||
StringArray inputs = MidiInput::getDevices(); | |||
int idx = inputs.indexOf(mgr.getInput()); | |||
idx = idx == -1 ? 0 : idx + 1; | |||
@@ -260,7 +526,7 @@ void ParamDialog::setDialogValues(Controllers &c, SysexComm &mgr, int reso, bool | |||
sysexOut->setSelectedItemIndex(idx); | |||
engineReso->setSelectedItemIndex(reso); | |||
showKeyboard->setToggleState(showKey, dontSendNotification); | |||
showKeyboard->setToggleState(showKey, NotificationType::dontSendNotification); | |||
} | |||
bool ParamDialog::getDialogValues(Controllers &c, SysexComm &mgr, int *reso, bool *showKey) { | |||
@@ -268,11 +534,35 @@ bool ParamDialog::getDialogValues(Controllers &c, SysexComm &mgr, int *reso, boo | |||
c.values_[kControllerPitchRange] = pitchRange->getValue(); | |||
c.values_[kControllerPitchStep] = pitchStep->getValue(); | |||
c.wheel.range = whlRange->getValue(); | |||
c.wheel.pitch = whlPitch->getToggleState(); | |||
c.wheel.amp = whlAmp->getToggleState(); | |||
c.wheel.eg = whlEg->getToggleState(); | |||
c.foot.range = ftRange->getValue(); | |||
c.foot.pitch = ftPitch->getToggleState(); | |||
c.foot.amp = ftAmp->getToggleState(); | |||
c.foot.eg = ftEg->getToggleState(); | |||
c.breath.range = brRange->getValue(); | |||
c.breath.pitch = brPitch->getToggleState(); | |||
c.breath.amp = brAmp->getToggleState(); | |||
c.breath.eg = brEg->getToggleState(); | |||
c.at.range = atRange->getValue(); | |||
c.at.pitch = atPitch->getToggleState(); | |||
c.at.amp = atAmp->getToggleState(); | |||
c.at.eg = atEg->getToggleState(); | |||
c.refresh(); | |||
ret &= mgr.setInput(sysexIn->getItemText(sysexIn->getSelectedItemIndex())); | |||
ret &= mgr.setOutput(sysexOut->getItemText(sysexOut->getSelectedItemIndex())); | |||
mgr.setChl(sysexChl->getValue() - 1); | |||
*reso = engineReso->getSelectedItemIndex(); | |||
// *showKey = showKeyboard->getToggleStateValue() == Button::ButtonState::buttonDown; | |||
*showKey = showKeyboard->getToggleState(); | |||
return ret; | |||
} | |||
@@ -292,7 +582,7 @@ BEGIN_JUCER_METADATA | |||
<JUCER_COMPONENT documentType="Component" className="ParamDialog" componentName="" | |||
parentClasses="public Component" constructorParams="" variableInitialisers="" | |||
snapPixels="8" snapActive="1" snapShown="1" overlayOpacity="0.330" | |||
fixedSize="1" initialWidth="350" initialHeight="350"> | |||
fixedSize="1" initialWidth="710" initialHeight="350"> | |||
<BACKGROUND backgroundColour="ff3c322f"> | |||
<TEXT pos="20 16 276 23" fill="solid: ffffffff" hasStroke="0" text="Pitch Bend Range" | |||
fontname="Default font" fontsize="15" bold="0" italic="0" justification="33"/> | |||
@@ -310,6 +600,21 @@ BEGIN_JUCER_METADATA | |||
<RECT pos="22 195 306 1" fill="solid: ff000000" hasStroke="0"/> | |||
<TEXT pos="20 96 276 23" fill="solid: ffffffff" hasStroke="0" text="Show Keyboard" | |||
fontname="Default font" fontsize="15" bold="0" italic="0" justification="33"/> | |||
<RECT pos="352 11 1 325" fill="solid: ff000000" hasStroke="0"/> | |||
<TEXT pos="368 16 276 23" fill="solid: ffffffff" hasStroke="0" text="Wheel" | |||
fontname="Default font" fontsize="15" bold="0" italic="0" justification="33"/> | |||
<TEXT pos="368 96 276 23" fill="solid: ffffffff" hasStroke="0" text="Foot" | |||
fontname="Default font" fontsize="15" bold="0" italic="0" justification="33"/> | |||
<TEXT pos="368 56 276 23" fill="solid: ffffffff" hasStroke="0" text="Breath" | |||
fontname="Default font" fontsize="15" bold="0" italic="0" justification="33"/> | |||
<TEXT pos="368 136 276 23" fill="solid: ffffffff" hasStroke="0" text="After Touch" | |||
fontname="Default font" fontsize="15" bold="0" italic="0" justification="33"/> | |||
<TEXT pos="533 163 48 23" fill="solid: ffffffff" hasStroke="0" text="PITCH" | |||
fontname="Default font" fontsize="15" bold="0" italic="0" justification="33"/> | |||
<TEXT pos="589 163 48 23" fill="solid: ffffffff" hasStroke="0" text="AMP" | |||
fontname="Default font" fontsize="15" bold="0" italic="0" justification="33"/> | |||
<TEXT pos="645 163 48 23" fill="solid: ffffffff" hasStroke="0" text="EG BIAS" | |||
fontname="Default font" fontsize="15" bold="0" italic="0" justification="33"/> | |||
</BACKGROUND> | |||
<SLIDER name="pitchRange" id="7409be5a8dfaa91" memberName="pitchRange" | |||
virtualName="" explicitFocusOrder="0" pos="264 16 72 24" min="0" | |||
@@ -331,11 +636,63 @@ BEGIN_JUCER_METADATA | |||
textBoxWidth="80" textBoxHeight="20" skewFactor="1"/> | |||
<COMBOBOX name="new combo box" id="4087ff978c3d9e8d" memberName="engineReso" | |||
virtualName="" explicitFocusOrder="0" pos="160 156 168 24" editable="0" | |||
layout="33" items="Modern (Direct) Mark I OPL Series" | |||
layout="33" items="Modern (24-bit) Mark I OPL Series" | |||
textWhenNonSelected="" textWhenNoItems="(no choices)"/> | |||
<TOGGLEBUTTON name="showKeyboard" id="c963d2cb8e49ffd7" memberName="showKeyboard" | |||
virtualName="" explicitFocusOrder="0" pos="264 96 56 24" buttonText="" | |||
connectedEdges="0" needsCallback="0" radioGroupId="0" state="0"/> | |||
<SLIDER name="whlRange" id="3d6522f5f581e580" memberName="whlRange" virtualName="" | |||
explicitFocusOrder="0" pos="448 16 72 24" min="0" max="99" int="1" | |||
style="RotaryVerticalDrag" textBoxPos="TextBoxLeft" textBoxEditable="1" | |||
textBoxWidth="80" textBoxHeight="20" skewFactor="1"/> | |||
<SLIDER name="ftRange" id="cf553f74c3fb0d12" memberName="ftRange" virtualName="" | |||
explicitFocusOrder="0" pos="448 56 72 24" min="0" max="99" int="1" | |||
style="RotaryVerticalDrag" textBoxPos="TextBoxLeft" textBoxEditable="1" | |||
textBoxWidth="80" textBoxHeight="20" skewFactor="1"/> | |||
<SLIDER name="brRange" id="c4aa6814f75016a7" memberName="brRange" virtualName="" | |||
explicitFocusOrder="0" pos="448 96 72 24" min="0" max="99" int="1" | |||
style="RotaryVerticalDrag" textBoxPos="TextBoxLeft" textBoxEditable="1" | |||
textBoxWidth="80" textBoxHeight="20" skewFactor="1"/> | |||
<SLIDER name="atRange" id="d0aa1ebb24284577" memberName="atRange" virtualName="" | |||
explicitFocusOrder="0" pos="448 136 72 24" min="0" max="99" int="1" | |||
style="RotaryVerticalDrag" textBoxPos="TextBoxLeft" textBoxEditable="1" | |||
textBoxWidth="80" textBoxHeight="20" skewFactor="1"/> | |||
<TOGGLEBUTTON name="whlEg" id="d8242ae592c912a" memberName="whlEg" virtualName="" | |||
explicitFocusOrder="0" pos="640 16 56 24" buttonText="" connectedEdges="0" | |||
needsCallback="1" radioGroupId="0" state="0"/> | |||
<TOGGLEBUTTON name="ftEg" id="69d816607bd71cb0" memberName="ftEg" virtualName="" | |||
explicitFocusOrder="0" pos="640 56 56 24" buttonText="" connectedEdges="0" | |||
needsCallback="1" radioGroupId="0" state="0"/> | |||
<TOGGLEBUTTON name="brEg" id="ba89ae54d676983f" memberName="brEg" virtualName="" | |||
explicitFocusOrder="0" pos="640 96 56 24" buttonText="" connectedEdges="0" | |||
needsCallback="1" radioGroupId="0" state="0"/> | |||
<TOGGLEBUTTON name="atEg" id="371934a58ce5f1bc" memberName="atEg" virtualName="" | |||
explicitFocusOrder="0" pos="640 136 56 24" buttonText="" connectedEdges="0" | |||
needsCallback="1" radioGroupId="0" state="0"/> | |||
<TOGGLEBUTTON name="whlAmp" id="3d4e46e63c3ddd86" memberName="whlAmp" virtualName="" | |||
explicitFocusOrder="0" pos="584 16 56 24" buttonText="" connectedEdges="0" | |||
needsCallback="1" radioGroupId="0" state="0"/> | |||
<TOGGLEBUTTON name="ftAmp" id="f631892e209b094a" memberName="ftAmp" virtualName="" | |||
explicitFocusOrder="0" pos="584 56 56 24" buttonText="" connectedEdges="0" | |||
needsCallback="1" radioGroupId="0" state="0"/> | |||
<TOGGLEBUTTON name="brAmp" id="d0a68d37220638f1" memberName="brAmp" virtualName="" | |||
explicitFocusOrder="0" pos="584 96 56 24" buttonText="" connectedEdges="0" | |||
needsCallback="1" radioGroupId="0" state="0"/> | |||
<TOGGLEBUTTON name="atAmp" id="4220c7b22e7845ea" memberName="atAmp" virtualName="" | |||
explicitFocusOrder="0" pos="584 136 56 24" buttonText="" connectedEdges="0" | |||
needsCallback="1" radioGroupId="0" state="0"/> | |||
<TOGGLEBUTTON name="whlPitch" id="b7a626ec1e45af16" memberName="whlPitch" virtualName="" | |||
explicitFocusOrder="0" pos="528 16 56 24" buttonText="" connectedEdges="0" | |||
needsCallback="1" radioGroupId="0" state="0"/> | |||
<TOGGLEBUTTON name="ftPitch" id="1acedf6f16a5a3" memberName="ftPitch" virtualName="" | |||
explicitFocusOrder="0" pos="528 56 56 24" buttonText="" connectedEdges="0" | |||
needsCallback="1" radioGroupId="0" state="0"/> | |||
<TOGGLEBUTTON name="brPitch" id="23fa82533e004b96" memberName="brPitch" virtualName="" | |||
explicitFocusOrder="0" pos="528 96 56 24" buttonText="" connectedEdges="0" | |||
needsCallback="1" radioGroupId="0" state="0"/> | |||
<TOGGLEBUTTON name="atPitch" id="43805c6a4673e291" memberName="atPitch" virtualName="" | |||
explicitFocusOrder="0" pos="528 136 56 24" buttonText="" connectedEdges="0" | |||
needsCallback="1" radioGroupId="0" state="0"/> | |||
</JUCER_COMPONENT> | |||
END_JUCER_METADATA | |||
@@ -7,12 +7,12 @@ | |||
the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded | |||
and re-saved. | |||
Created with Introjucer version: 3.1.0 | |||
Created with Introjucer version: 3.2.0 | |||
------------------------------------------------------------------------------ | |||
The Introjucer is part of the JUCE library - "Jules' Utility Class Extensions" | |||
Copyright 2004-13 by Raw Material Software Ltd. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
============================================================================== | |||
*/ | |||
@@ -38,7 +38,8 @@ | |||
*/ | |||
class ParamDialog : public Component, | |||
public SliderListener, | |||
public ComboBoxListener | |||
public ComboBoxListener, | |||
public ButtonListener | |||
{ | |||
public: | |||
//============================================================================== | |||
@@ -55,6 +56,7 @@ public: | |||
void resized(); | |||
void sliderValueChanged (Slider* sliderThatWasMoved); | |||
void comboBoxChanged (ComboBox* comboBoxThatHasChanged); | |||
void buttonClicked (Button* buttonThatWasClicked); | |||
@@ -70,6 +72,22 @@ private: | |||
ScopedPointer<Slider> sysexChl; | |||
ScopedPointer<ComboBox> engineReso; | |||
ScopedPointer<ToggleButton> showKeyboard; | |||
ScopedPointer<Slider> whlRange; | |||
ScopedPointer<Slider> ftRange; | |||
ScopedPointer<Slider> brRange; | |||
ScopedPointer<Slider> atRange; | |||
ScopedPointer<ToggleButton> whlEg; | |||
ScopedPointer<ToggleButton> ftEg; | |||
ScopedPointer<ToggleButton> brEg; | |||
ScopedPointer<ToggleButton> atEg; | |||
ScopedPointer<ToggleButton> whlAmp; | |||
ScopedPointer<ToggleButton> ftAmp; | |||
ScopedPointer<ToggleButton> brAmp; | |||
ScopedPointer<ToggleButton> atAmp; | |||
ScopedPointer<ToggleButton> whlPitch; | |||
ScopedPointer<ToggleButton> ftPitch; | |||
ScopedPointer<ToggleButton> brPitch; | |||
ScopedPointer<ToggleButton> atPitch; | |||
//============================================================================== | |||
@@ -1,6 +1,6 @@ | |||
/** | |||
* | |||
* Copyright (c) 2014-2015 Pascal Gauthier. | |||
* Copyright (c) 2014-2017 Pascal Gauthier. | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -28,7 +28,7 @@ | |||
#include <fstream> | |||
using namespace ::std; | |||
uint8_t sysexChecksum(const char *sysex, int size) { | |||
uint8_t sysexChecksum(const uint8_t *sysex, int size) { | |||
int sum = 0; | |||
int i; | |||
@@ -36,71 +36,14 @@ uint8_t sysexChecksum(const char *sysex, int size) { | |||
return sum & 0x7F; | |||
} | |||
String normalizeSysexName(const char *sysexName) { | |||
char buffer[11]; | |||
memcpy(buffer, sysexName, 10); | |||
for (int j = 0; j < 10; j++) { | |||
char c = (unsigned char) buffer[j]; | |||
switch (c) { | |||
case 92: | |||
c = 'Y'; | |||
break; /* yen */ | |||
case 126: | |||
c = '>'; | |||
break; /* >> */ | |||
case 127: | |||
c = '<'; | |||
break; /* << */ | |||
default: | |||
if (c < 32 || c > 127) | |||
c = 32; | |||
break; | |||
} | |||
buffer[j] = c; | |||
} | |||
buffer[10] = 0; | |||
return String(buffer); | |||
} | |||
void extractProgramNames(const char *block, StringArray &dest) { | |||
dest.clear(); | |||
for (int i = 0; i < 32; i++) { | |||
dest.add(String(normalizeSysexName(block + ((i * 128) + 118)))); | |||
} | |||
} | |||
void exportSysexCart(char *dest, char *src, char sysexChl) { | |||
uint8_t header[] = { 0xF0, 0x43, 0x00, 0x09, 0x20, 0x00 }; | |||
header[2] = sysexChl; | |||
memcpy(dest, header, 6); | |||
// copy 32 voices | |||
memcpy(dest+6, src, 4096); | |||
// make checksum for dump | |||
uint8_t footer[] = { sysexChecksum(src, 4096), 0xF7 }; | |||
memcpy(dest+4102, footer, 2); | |||
} | |||
void exportSysexPgm(char *dest, char *src, char sysexChl) { | |||
void exportSysexPgm(uint8_t *dest, uint8_t *src) { | |||
uint8_t header[] = { 0xF0, 0x43, 0x00, 0x00, 0x01, 0x1B }; | |||
header[2] = sysexChl; | |||
memcpy(dest, header, 6); | |||
// copy 1 unpacked voices | |||
memcpy(dest+6, src, 155); | |||
// put some logic to "mute" an operator if the level is 0 | |||
// make checksum for dump | |||
uint8_t footer[] = { sysexChecksum(src, 155), 0xF7 }; | |||
@@ -110,8 +53,8 @@ void exportSysexPgm(char *dest, char *src, char sysexChl) { | |||
/** | |||
* Pack a program into a 32 packed sysex | |||
*/ | |||
void packProgram(uint8_t *dest, uint8_t *src, int idx, String name) { | |||
uint8_t *bulk = dest + (idx * 128); | |||
void Cartridge::packProgram(uint8_t *src, int idx, String name, char *opSwitch) { | |||
uint8_t *bulk = voiceData + 6 + (idx * 128); | |||
for(int op = 0; op < 6; op++) { | |||
// eg rate and level, brk pt, depth, scaling | |||
@@ -125,7 +68,10 @@ void packProgram(uint8_t *dest, uint8_t *src, int idx, String name) { | |||
// kvs_ams | |||
bulk[pp+13] = (src[up+14]&0x03) | ((src[up+15]&0x07) << 2); | |||
// output lvl | |||
bulk[pp+14] = src[up+16]; | |||
if ( opSwitch[op] == '0' ) | |||
bulk[pp+14] = 0; | |||
else | |||
bulk[pp+14] = src[up+16]; | |||
// fcoarse_mode | |||
bulk[pp+15] = (src[up+17]&0x01) | ((src[up+18]&0x1f) << 1); | |||
// fine freq | |||
@@ -171,8 +117,9 @@ char normparm(char value, char max, int id) { | |||
return v; | |||
} | |||
void unpackProgramFromSysex(char *unpackPgm, char *sysexCart, int idx) { | |||
char *bulk = sysexCart + (idx * 128); | |||
void Cartridge::unpackProgram(uint8_t *unpackPgm, int idx) { | |||
// TODO put this in uint8_t :D | |||
char *bulk = (char *)voiceData + 6 + (idx * 128); | |||
for (int op = 0; op < 6; op++) { | |||
// eg rate and level, brk pt, depth, scaling | |||
@@ -212,61 +159,57 @@ void unpackProgramFromSysex(char *unpackPgm, char *sysexCart, int idx) { | |||
unpackPgm[142] = (lpms_lfw_lks >> 1) & 7; | |||
unpackPgm[143] = lpms_lfw_lks >> 4; | |||
memcpy(unpackPgm + 144, bulk + 117, 11); // transpose, name | |||
unpackPgm[155] = 1; // operator on/off | |||
unpackPgm[156] = 1; | |||
unpackPgm[157] = 1; | |||
unpackPgm[158] = 1; | |||
unpackPgm[159] = 1; | |||
unpackPgm[160] = 1; | |||
unpackPgm[155] = 63; // operator on/off (DEPRECATED) | |||
} | |||
void DexedAudioProcessor::unpackProgram(int idx) { | |||
unpackProgramFromSysex(data, sysex, idx); | |||
void DexedAudioProcessor::loadCartridge(Cartridge &sysex) { | |||
currentCart = sysex; | |||
currentCart.getProgramNames(programNames); | |||
} | |||
int DexedAudioProcessor::importSysex(const char *imported) { | |||
memcpy(sysex, imported + 6, 4096); | |||
uint8_t checksum = sysexChecksum(((char *) &sysex), 4096); | |||
extractProgramNames(sysex, programNames); | |||
if ( checksum != imported[4102] ) { | |||
TRACE("sysex import checksum doesnt match %d != %d", checksum, imported[4102]); | |||
return 1; | |||
} | |||
return 0; | |||
void DexedAudioProcessor::packOpSwitch() { | |||
char value = (controllers.opSwitch[5] == '1') << 5; | |||
value += (controllers.opSwitch[4] == '1') << 4; | |||
value += (controllers.opSwitch[3] == '1') << 3; | |||
value += (controllers.opSwitch[2] == '1') << 2; | |||
value += (controllers.opSwitch[1] == '1') << 1; | |||
value += (controllers.opSwitch[0] == '1'); | |||
data[155] = value; | |||
} | |||
void DexedAudioProcessor::updateProgramFromSysex(const uint8 *rawdata) { | |||
memcpy(data, rawdata, 161); | |||
void DexedAudioProcessor::unpackOpSwitch(char packOpValue) { | |||
controllers.opSwitch[5] = (packOpValue & 32) + 48; | |||
controllers.opSwitch[4] = (packOpValue & 16) + 48; | |||
controllers.opSwitch[3] = (packOpValue & 8) + 48; | |||
controllers.opSwitch[2] = (packOpValue & 4) + 48; | |||
controllers.opSwitch[1] = (packOpValue & 2) + 48; | |||
controllers.opSwitch[0] = (packOpValue & 1) + 48; | |||
} | |||
void DexedAudioProcessor::updateProgramFromSysex(const uint8_t *rawdata) { | |||
memcpy(data, rawdata, 155); | |||
unpackOpSwitch(rawdata[155]); | |||
lfo.reset(data + 137); | |||
triggerAsyncUpdate(); | |||
} | |||
void DexedAudioProcessor::setupStartupCart() { | |||
char syx_data[4104]; | |||
memset(&syx_data, 0, 4104); | |||
File startup = dexedCartDir.getChildFile("Dexed_01.syx"); | |||
if ( currentCart.load(startup) != -1 ) | |||
return; | |||
if ( startup.exists() ) { | |||
FileInputStream *fis = startup.createInputStream(); | |||
if ( fis == nullptr ) { | |||
TRACE("unable to open default cartridge"); | |||
return; | |||
} | |||
fis->read(syx_data, 4104); | |||
delete fis; | |||
} else { | |||
// The user deleted the file :/, load from the builtin zip file. | |||
MemoryInputStream *mis = new MemoryInputStream(BinaryData::builtin_pgm_zip, BinaryData::builtin_pgm_zipSize, false); | |||
ZipFile *builtin_pgm = new ZipFile(mis, true); | |||
InputStream *is = builtin_pgm->createStreamForEntry(builtin_pgm->getIndexOfFileName(("Dexed_01.syx"))); | |||
is->read(syx_data, 4104); | |||
delete is; | |||
delete builtin_pgm; | |||
} | |||
importSysex((char *) &syx_data); | |||
// The user deleted the file :/, load from the builtin zip file. | |||
MemoryInputStream *mis = new MemoryInputStream(BinaryData::builtin_pgm_zip, BinaryData::builtin_pgm_zipSize, false); | |||
ZipFile *builtin_pgm = new ZipFile(mis, true); | |||
InputStream *is = builtin_pgm->createStreamForEntry(builtin_pgm->getIndexOfFileName(("Dexed_01.syx"))); | |||
Cartridge init; | |||
if ( init.load(*is) != -1 ) | |||
loadCartridge(init); | |||
delete is; | |||
delete builtin_pgm; | |||
} | |||
void DexedAudioProcessor::resetToInitVoice() { | |||
@@ -303,9 +246,10 @@ void DexedAudioProcessor::pasteEnvFromClipboard(int destOp) { | |||
} | |||
void DexedAudioProcessor::sendCurrentSysexProgram() { | |||
uint8_t raw[167]; | |||
uint8_t raw[163]; | |||
exportSysexPgm((char *) raw, data, sysexComm.getChl()); | |||
packOpSwitch(); | |||
exportSysexPgm(raw, data); | |||
if ( sysexComm.isOutputActive() ) { | |||
sysexComm.send(MidiMessage(raw, 163)); | |||
} | |||
@@ -314,7 +258,7 @@ void DexedAudioProcessor::sendCurrentSysexProgram() { | |||
void DexedAudioProcessor::sendCurrentSysexCartridge() { | |||
uint8_t raw[4104]; | |||
exportSysexCart((char *) raw, (char *) &sysex, sysexComm.getChl()); | |||
currentCart.saveVoice(raw); | |||
if ( sysexComm.isOutputActive() ) { | |||
sysexComm.send(MidiMessage(raw, 4104)); | |||
} | |||
@@ -323,18 +267,27 @@ void DexedAudioProcessor::sendCurrentSysexCartridge() { | |||
void DexedAudioProcessor::sendSysexCartridge(File cart) { | |||
if ( ! sysexComm.isOutputActive() ) | |||
return; | |||
String f = cart.getFullPathName(); | |||
uint8_t syx_data[4104]; | |||
ifstream fp_in(f.toRawUTF8(), ios::binary); | |||
if (fp_in.fail()) { | |||
FileInputStream *fis = cart.createInputStream(); | |||
if ( fis == NULL ) { | |||
String f = cart.getFullPathName(); | |||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | |||
"Error", | |||
"Unable to open: " + f); | |||
} | |||
uint8 syx_data[65535]; | |||
int sz = fis->read(syx_data, 65535); | |||
delete fis; | |||
if (syx_data[0] != 0xF0) { | |||
String f = cart.getFullPathName(); | |||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | |||
"Error", | |||
"File: " + f + " doesn't seems to contain any sysex data"); | |||
return; | |||
} | |||
fp_in.read((char *)syx_data, 4104); | |||
fp_in.close(); | |||
sysexComm.send(MidiMessage(syx_data, 4104)); | |||
sysexComm.send(MidiMessage(syx_data, sz)); | |||
} | |||
@@ -359,15 +312,24 @@ void DexedAudioProcessor::getStateInformation(MemoryBlock& destData) { | |||
dexedState.setAttribute("currentProgram", currentProgram); | |||
dexedState.setAttribute("monoMode", monoMode); | |||
dexedState.setAttribute("engineType", (int) engineType); | |||
dexedState.setAttribute("masterTune", controllers.masterTune); | |||
dexedState.setAttribute("opSwitch", controllers.opSwitch); | |||
char mod_cfg[15]; | |||
controllers.wheel.setConfig(mod_cfg); | |||
dexedState.setAttribute("wheelMod", mod_cfg); | |||
controllers.foot.setConfig(mod_cfg); | |||
dexedState.setAttribute("footMod", mod_cfg); | |||
controllers.breath.setConfig(mod_cfg); | |||
dexedState.setAttribute("breathMod", mod_cfg); | |||
controllers.at.setConfig(mod_cfg); | |||
dexedState.setAttribute("aftertouchMod", mod_cfg); | |||
if ( activeFileCartridge.exists() ) | |||
dexedState.setAttribute("activeFileCartridge", activeFileCartridge.getFullPathName()); | |||
char sysex_blob[4104]; | |||
exportSysexCart((char *) &sysex_blob, (char *) sysex, 0); | |||
NamedValueSet blobSet; | |||
blobSet.set("sysex", var((void *) &sysex_blob, 4104)); | |||
blobSet.set("sysex", var((void *) currentCart.getVoiceSysex(), 4104)); | |||
blobSet.set("program", var((void *) &data, 161)); | |||
blobSet.copyToXmlAttributes(*dexedBlob); | |||
@@ -391,8 +353,23 @@ void DexedAudioProcessor::setStateInformation(const void* source, int sizeInByte | |||
fx.uiGain = root->getDoubleAttribute("gain"); | |||
currentProgram = root->getIntAttribute("currentProgram"); | |||
setEngineType(root->getIntAttribute("engineType", 0)); | |||
String opSwitchValue = root->getStringAttribute("opSwitch"); | |||
if ( opSwitchValue.length() != 6 ) { | |||
strcpy(controllers.opSwitch, "111111"); | |||
} else { | |||
strncpy(controllers.opSwitch, opSwitchValue.toRawUTF8(), 6); | |||
} | |||
controllers.wheel.parseConfig(root->getStringAttribute("wheelMod").toRawUTF8()); | |||
controllers.foot.parseConfig(root->getStringAttribute("footMod").toRawUTF8()); | |||
controllers.breath.parseConfig(root->getStringAttribute("breathMod").toRawUTF8()); | |||
controllers.at.parseConfig(root->getStringAttribute("aftertouchMod").toRawUTF8()); | |||
controllers.refresh(); | |||
setEngineType(root->getIntAttribute("engineType", 1)); | |||
monoMode = root->getIntAttribute("monoMode", 0); | |||
controllers.masterTune = root->getIntAttribute("masterTune", 0); | |||
File possibleCartridge = File(root->getStringAttribute("activeFileCartridge")); | |||
if ( possibleCartridge.exists() ) | |||
@@ -415,7 +392,9 @@ void DexedAudioProcessor::setStateInformation(const void* source, int sizeInByte | |||
return; | |||
} | |||
importSysex((char *) sysex_blob.getBinaryData()->getData()); | |||
Cartridge cart; | |||
cart.load((uint8 *)sysex_blob.getBinaryData()->getData(), 4104); | |||
loadCartridge(cart); | |||
memcpy(data, program.getBinaryData()->getData(), 161); | |||
lastStateSave = (long) time(NULL); | |||
@@ -427,19 +406,32 @@ File DexedAudioProcessor::dexedAppDir; | |||
File DexedAudioProcessor::dexedCartDir; | |||
void DexedAudioProcessor::resolvAppDir() { | |||
#if JUCE_MAC || JUCE_IOS | |||
File parent = File::getSpecialLocation(File::currentExecutableFile).getParentDirectory().getParentDirectory().getParentDirectory().getSiblingFile("Dexed"); | |||
#if JUCE_MAC || JUCE_IOS | |||
dexedAppDir = File("~/Library/Application Support/DigitalSuburban/Dexed"); | |||
#elif JUCE_WINDOWS | |||
dexedAppDir = File::getSpecialLocation(File::userApplicationDataDirectory).getChildFile("DigitalSuburban").getChildFile("Dexed"); | |||
#else | |||
char *xdgHome = getenv("XDG_DATA_HOME"); | |||
if ( xdgHome == nullptr ) { | |||
dexedAppDir = File("~/.local/share").getChildFile("DigitalSuburban").getChildFile("Dexed"); | |||
} else { | |||
dexedAppDir = File(xdgHome).getChildFile("DigitalSuburban").getChildFile("Dexed"); | |||
} | |||
#endif | |||
if ( parent.isDirectory() ) { | |||
dexedAppDir = parent; | |||
} else { | |||
dexedAppDir = File("~/Library/Application Support/DigitalSuburban/Dexed"); | |||
} | |||
#elif JUCE_WINDOWS | |||
if ( File::getSpecialLocation(File::currentExecutableFile).getSiblingFile("Dexed").isDirectory() ) { | |||
dexedAppDir = File::getSpecialLocation(File::currentExecutableFile).getSiblingFile("Dexed"); | |||
} else { | |||
dexedAppDir = File::getSpecialLocation(File::userApplicationDataDirectory).getChildFile("DigitalSuburban").getChildFile("Dexed"); | |||
} | |||
#else | |||
if ( File::getSpecialLocation(File::currentExecutableFile).getSiblingFile("Dexed").isDirectory() ) { | |||
dexedAppDir = File::getSpecialLocation(File::currentExecutableFile).getSiblingFile("Dexed"); | |||
} else { | |||
char *xdgHome = getenv("XDG_DATA_HOME"); | |||
if ( xdgHome == nullptr ) { | |||
dexedAppDir = File("~/.local/share").getChildFile("DigitalSuburban").getChildFile("Dexed"); | |||
} else { | |||
dexedAppDir = File(xdgHome).getChildFile("DigitalSuburban").getChildFile("Dexed"); | |||
} | |||
} | |||
#endif | |||
if ( ! dexedAppDir.exists() ) { | |||
dexedAppDir.createDirectory(); | |||
@@ -1,6 +1,6 @@ | |||
/** | |||
* | |||
* Copyright (c) 2014-2015 Pascal Gauthier. | |||
* Copyright (c) 2014-2016 Pascal Gauthier. | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -22,49 +22,229 @@ | |||
#define PLUGINDATA_H_INCLUDED | |||
#include "JuceHeader.h" | |||
#define SYSEX_SIZE 4104 | |||
#include <stdint.h> | |||
#include <string.h> | |||
#include "Dexed.h" | |||
enum UnpackedOffset { | |||
egRate, | |||
egLevel = 4, | |||
breakpoint = 8, | |||
lScaleDepth, | |||
rScaleDepth, | |||
lKeyScale, | |||
rKeyScale, | |||
rateScaling, | |||
keyVelocity, | |||
outputLevel, | |||
mode, | |||
fCoarse, | |||
fFine, | |||
oscDetune, | |||
uint8_t sysexChecksum(const uint8_t *sysex, int size); | |||
void exportSysexPgm(uint8_t *dest, uint8_t *src); | |||
// Global values | |||
pitchEgRate = 126, | |||
pitchEgLevel = 130, | |||
algorythm = 134, | |||
feedback, | |||
oscKeySync, | |||
lfoSpeed, | |||
lfoDelay, | |||
lfoPmDepth, | |||
lfoAmDepth, | |||
lfoKeySync, | |||
lfoWave, | |||
middleC, | |||
pModeSens, | |||
osc6state | |||
}; | |||
#define SYSEX_HEADER { 0xF0, 0x43, 0x00, 0x09, 0x20, 0x00 } | |||
#define SYSEX_SIZE 4104 | |||
String normalizeSysexName(const char *sysexName); | |||
uint8_t sysexChecksum(const char *sysex, int size); | |||
void extractProgramNames(const char *block, StringArray &dest); | |||
void exportSysexCart(char *dest, char *src, char sysexChl); | |||
void exportSysexPgm(char *dest, char *src, char sysexChl); | |||
void packProgram(uint8_t *dest, uint8_t *src, int idx, String name); | |||
void unpackProgramFromSysex(char *unpackPgm, char *sysexCart, int idx); | |||
class Cartridge { | |||
uint8_t voiceData[SYSEX_SIZE]; | |||
uint8_t perfData[SYSEX_SIZE]; | |||
void setHeader() { | |||
uint8 voiceHeader[] = SYSEX_HEADER; | |||
memcpy(voiceData, voiceHeader, 6); | |||
voiceData[4102] = sysexChecksum(voiceData+6, 4096); | |||
voiceData[4103] = 0xF7; | |||
} | |||
public: | |||
Cartridge() { } | |||
Cartridge(const Cartridge &cpy) { | |||
memcpy(voiceData, cpy.voiceData, SYSEX_SIZE); | |||
memcpy(perfData, cpy.perfData, SYSEX_SIZE); | |||
} | |||
static String normalizePgmName(const char *sysexName) { | |||
char buffer[11]; | |||
memcpy(buffer, sysexName, 10); | |||
for (int j = 0; j < 10; j++) { | |||
char c = (unsigned char) buffer[j]; | |||
switch (c) { | |||
case 92: | |||
c = 'Y'; | |||
break; /* yen */ | |||
case 126: | |||
c = '>'; | |||
break; /* >> */ | |||
case 127: | |||
c = '<'; | |||
break; /* << */ | |||
default: | |||
if (c < 32 || c > 127) | |||
c = 32; | |||
break; | |||
} | |||
buffer[j] = c; | |||
} | |||
buffer[10] = 0; | |||
return String(buffer); | |||
} | |||
int load(File f) { | |||
FileInputStream *fis = f.createInputStream(); | |||
if ( fis == NULL ) | |||
return -1; | |||
int rc = load(*fis); | |||
delete fis; | |||
return rc; | |||
} | |||
/** | |||
* Loads sysex stream | |||
* Returns 0 if it was parsed sucessfully | |||
* Returns -1 if it cannot open the stream | |||
*/ | |||
int load(InputStream &fis) { | |||
uint8 buffer[65535]; | |||
int sz = fis.read(buffer, 65535); | |||
if ( sz == 0 ) | |||
return -1; | |||
return load(buffer, sz); | |||
} | |||
/** | |||
* Loads sysex buffer | |||
* Returns 0 if it was parsed sucessfully | |||
* Returns 1 if sysex checksum didn't match | |||
* Returns 2 if no sysex data found, probably random data | |||
*/ | |||
int load(const uint8_t *stream, int size) { | |||
const uint8 *pos = stream; | |||
if ( size < 4096 ) { | |||
memcpy(voiceData+6, pos, size); | |||
TRACE("too small sysex rc=2"); | |||
return 2; | |||
} | |||
if ( pos[0] != 0xF0 ) { | |||
// it is not, just copy the first 4096 bytes | |||
memcpy(voiceData + 6, pos, 4096); | |||
TRACE("stream is not a sysex rc=2"); | |||
return 2; | |||
} | |||
// limit the size of the sysex scan | |||
if ( size > 65535 ) | |||
size = 65535; | |||
// we loop until we find something that looks like a DX7 cartridge (based on size) | |||
while(size >= 4104) { | |||
// it was a sysex first, now random data; return random | |||
if ( pos[0] != 0xF0 ) { | |||
memcpy(voiceData + 6, stream, 4096); | |||
TRACE("stream was a sysex, but not anymore rc=2"); | |||
return 2; | |||
} | |||
// check if this is the size of a DX7 sysex cartridge | |||
for(int i=0;i<size;i++) { | |||
if ( pos[i] == 0xF7 ) { | |||
if ( i == SYSEX_SIZE - 1 ) { | |||
memcpy(voiceData, pos, SYSEX_SIZE); | |||
if ( sysexChecksum(voiceData + 6, 4096) == pos[4102] ) { | |||
TRACE("valid sysex found!"); | |||
return 0; | |||
} else { | |||
TRACE("sysex found, but checksum doesn't match rc=1"); | |||
return 1; | |||
} | |||
} | |||
size -= i; | |||
pos += i; | |||
TRACE("end of sysex with wrong DX size... still scanning stream: size=%d", i); | |||
break; | |||
} | |||
} | |||
TRACE("sysex stream parsed without any end message, skipping..."); | |||
break; | |||
} | |||
// it is a sysex, but doesn't seems to be related to any DX series ... | |||
memcpy(voiceData + 6, stream, 4096); | |||
TRACE("nothing in the sysex stream was DX related rc=2"); | |||
return 2; | |||
} | |||
int saveVoice(File f) { | |||
setHeader(); | |||
if ( ! f.existsAsFile() ) { | |||
// file doesn't exists, create it | |||
return f.replaceWithData(voiceData, SYSEX_SIZE); | |||
} | |||
FileInputStream *fis = f.createInputStream(); | |||
if ( fis == NULL ) | |||
return -1; | |||
uint8 buffer[65535]; | |||
int sz = fis->read(buffer, 65535); | |||
delete fis; | |||
// if the file is smaller than 4104, it probably needs to be overriden. | |||
if ( sz <= 4104 ) { | |||
return f.replaceWithData(voiceData, SYSEX_SIZE); | |||
} | |||
// To avoid to erase the performance data, we skip the sysex stream until | |||
// we see the header 0xF0, 0x43, 0x00, 0x09, 0x20, 0x00 | |||
int pos = 0; | |||
bool found = 0; | |||
while(pos < sz) { | |||
// corrupted sysex, erase everything : | |||
if ( buffer[pos] != 0xF0 ) | |||
return f.replaceWithData(voiceData, SYSEX_SIZE); | |||
uint8_t header[] = SYSEX_HEADER; | |||
if ( memcmp(buffer+pos, header, 6) ) { | |||
found = true; | |||
memcpy(buffer+pos, voiceData, SYSEX_SIZE); | |||
break; | |||
} else { | |||
for(;pos<sz;pos++) { | |||
if ( buffer[pos] == 0xF7 ) | |||
break; | |||
} | |||
} | |||
} | |||
if ( ! found ) | |||
return -1; | |||
return f.replaceWithData(buffer, sz); | |||
} | |||
void saveVoice(uint8_t *sysex) { | |||
setHeader(); | |||
memcpy(sysex, voiceData, SYSEX_SIZE); | |||
} | |||
char *getRawVoice() { | |||
return (char *) voiceData + 6; | |||
} | |||
char *getVoiceSysex() { | |||
setHeader(); | |||
return (char *) voiceData; | |||
} | |||
void getProgramNames(StringArray &dest) { | |||
dest.clear(); | |||
for (int i = 0; i < 32; i++) | |||
dest.add( normalizePgmName(getRawVoice() + ((i * 128) + 118)) ); | |||
} | |||
Cartridge operator =(const Cartridge other) { | |||
memcpy(voiceData, other.voiceData, SYSEX_SIZE); | |||
memcpy(perfData, other.perfData, SYSEX_SIZE); | |||
return *this; | |||
} | |||
void unpackProgram(uint8_t *unpackPgm, int idx); | |||
void packProgram(uint8_t *src, int idx, String name, char *opSwitch); | |||
}; | |||
#endif // PLUGINDATA_H_INCLUDED |
@@ -30,33 +30,6 @@ | |||
#include "msfa/fm_op_kernel.h" | |||
using namespace ::std; | |||
class AboutBox : public DialogWindow { | |||
public: | |||
Image about_png; | |||
AboutBox(Component *parent) : DialogWindow("About", Colour(0xFF000000), true) { | |||
setUsingNativeTitleBar(false); | |||
setAlwaysOnTop(true); | |||
about_png = ImageCache::getFromMemory(BinaryData::about_png, BinaryData::about_pngSize); | |||
setSize(about_png.getWidth(), about_png.getHeight()); | |||
centreAroundComponent (parent, getWidth(), getHeight()); | |||
} | |||
void closeButtonPressed() { | |||
setVisible (false); | |||
} | |||
void paint(Graphics &g) { | |||
g.drawImage (about_png, 0, 0, about_png.getWidth(), about_png.getHeight(), | |||
0, 0, about_png.getWidth(), about_png.getHeight()); | |||
g.setColour(Colour(0xFF000000)); | |||
String ver("Version " DEXED_VERSION " ; built date " __DATE__ ); | |||
g.drawSingleLineText(ver, 9, 118); | |||
} | |||
}; | |||
//============================================================================== | |||
DexedAudioProcessorEditor::DexedAudioProcessorEditor (DexedAudioProcessor* ownerFilter) | |||
: AudioProcessorEditor (ownerFilter), | |||
@@ -140,22 +113,28 @@ void DexedAudioProcessorEditor::cartShow() { | |||
void DexedAudioProcessorEditor::loadCart(File file) { | |||
String f = file.getFullPathName(); | |||
uint8_t syx_data[4104]; | |||
ifstream fp_in(f.toRawUTF8(), ios::binary); | |||
if (fp_in.fail()) { | |||
Cartridge cart; | |||
int rc = cart.load(file); | |||
if ( rc < 0 ) { | |||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | |||
"Error", | |||
"Unable to open: " + f); | |||
"Unable to open: " + file.getFullPathName()); | |||
return; | |||
} | |||
fp_in.read((char *)syx_data, 4104); | |||
fp_in.close(); | |||
if ( processor->importSysex((char *) &syx_data) ) { | |||
global.setSystemMessage(String("Unkown sysex format !?")); | |||
if ( rc != 0 ) { | |||
rc = AlertWindow::showOkCancelBox(AlertWindow::QuestionIcon, "Unable to find DX7 sysex cartridge in file", | |||
"This sysex file is not for the DX7 or it is corrupted. " | |||
"Do you still want to load this file as random data ?"); | |||
if ( rc == 0 ) | |||
return; | |||
} | |||
processor->setCurrentProgram(0); | |||
processor->loadCartridge(cart); | |||
rebuildProgramCombobox(); | |||
processor->setCurrentProgram(0); | |||
global.programs->setSelectedId(processor->getCurrentProgram()+1, dontSendNotification); | |||
processor->updateHostDisplay(); | |||
@@ -165,21 +144,12 @@ void DexedAudioProcessorEditor::loadCart(File file) { | |||
void DexedAudioProcessorEditor::saveCart() { | |||
File startFileName = processor->activeFileCartridge.exists() ? processor->activeFileCartridge : processor->dexedCartDir; | |||
FileChooser fc ("Export DX sysex...", processor->dexedCartDir, "*.syx", 1); | |||
FileChooser fc ("Export DX sysex...", processor->dexedCartDir, "*.syx;*.SYX", 1); | |||
if ( fc.browseForFileToSave(true) ) { | |||
String f = fc.getResults().getReference(0).getFullPathName(); | |||
char syx_data[4104]; | |||
exportSysexCart((char *) syx_data, (char *) &processor->sysex, 0); | |||
ofstream fp_out(f.toRawUTF8(), ios::binary); | |||
fp_out.write((char *)syx_data, 4104); | |||
fp_out.close(); | |||
if (fp_out.fail()) { | |||
if ( ! processor->currentCart.saveVoice(fc.getResults().getReference(0)) ) { | |||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | |||
"Error", | |||
"Unable to write: " + f); | |||
"Unable to write: " + fc.getResults().getReference(0).getFullPathName()); | |||
} | |||
} | |||
} | |||
@@ -187,7 +157,7 @@ void DexedAudioProcessorEditor::saveCart() { | |||
void DexedAudioProcessorEditor::parmShow() { | |||
int tp = processor->getEngineType(); | |||
AlertWindow window("Dexed Params","", AlertWindow::NoIcon, this); | |||
AlertWindow window("","", AlertWindow::NoIcon, this); | |||
ParamDialog param; | |||
param.setColour(AlertWindow::backgroundColourId, Colour(0x32FFFFFF)); | |||
param.setDialogValues(processor->controllers, processor->sysexComm, tp, processor->showKeyboard); | |||
@@ -250,6 +220,8 @@ void DexedAudioProcessorEditor::updateUI() { | |||
void DexedAudioProcessorEditor::rebuildProgramCombobox() { | |||
global.programs->clear(dontSendNotification); | |||
processor->currentCart.getProgramNames(processor->programNames); | |||
for(int i=0;i<processor->getNumPrograms();i++) { | |||
String id; | |||
id << (i+1) << ". " << processor->getProgramName(i); | |||
@@ -258,7 +230,7 @@ void DexedAudioProcessorEditor::rebuildProgramCombobox() { | |||
global.programs->setSelectedId(processor->getCurrentProgram()+1, dontSendNotification); | |||
String name = normalizeSysexName((const char *) processor->data+145); | |||
String name = Cartridge::normalizePgmName((const char *) processor->data+145); | |||
cartManager.setActiveProgram(processor->getCurrentProgram(), name); | |||
if ( name != processor->getProgramName(processor->getCurrentProgram()) ) | |||
global.programs->setText("**. " + name, dontSendNotification); | |||
@@ -267,11 +239,9 @@ void DexedAudioProcessorEditor::rebuildProgramCombobox() { | |||
} | |||
void DexedAudioProcessorEditor::storeProgram() { | |||
String currentName = normalizeSysexName((const char *) processor->data+145); | |||
char destSysex[4096]; | |||
String currentName = Cartridge::normalizePgmName((const char *) processor->data+145); | |||
Cartridge destSysex = processor->currentCart; | |||
File *externalFile = NULL; | |||
memcpy(&destSysex, processor->sysex, 4096); | |||
bool activeCartridgeFound = processor->activeFileCartridge.exists(); | |||
@@ -292,7 +262,7 @@ void DexedAudioProcessorEditor::storeProgram() { | |||
// TODO: fix the name length to 10 | |||
StringArray programs; | |||
extractProgramNames((char *) &destSysex, programs); | |||
destSysex.getProgramNames(programs); | |||
dialog.addComboBox("Dest", programs, "Program Destination"); | |||
@@ -318,12 +288,9 @@ void DexedAudioProcessorEditor::storeProgram() { | |||
if ( externalFile != NULL ) | |||
delete externalFile; | |||
MemoryBlock block; | |||
externalFile = new File(fc.getResults().getReference(0)); | |||
if ( externalFile->loadFileAsData(block) ) { | |||
block.copyTo(destSysex, 6, 4096); | |||
if ( destSysex.load(*externalFile) == 0 ) | |||
continue; | |||
} | |||
AlertWindow::showMessageBoxAsync(AlertWindow::WarningIcon, "Read error", "Unable to read file"); | |||
} | |||
} | |||
@@ -340,8 +307,7 @@ void DexedAudioProcessorEditor::storeProgram() { | |||
} | |||
if ( externalFile == NULL ) { | |||
packProgram((uint8_t *) processor->sysex, (uint8_t *) processor->data, programNum, programName); | |||
processor->programNames.set(programNum, programName); | |||
processor->currentCart.packProgram((uint8_t *) processor->data, programNum, programName, processor->controllers.opSwitch); | |||
rebuildProgramCombobox(); | |||
processor->setCurrentProgram(programNum); | |||
processor->updateHostDisplay(); | |||
@@ -349,24 +315,19 @@ void DexedAudioProcessorEditor::storeProgram() { | |||
int action = dialog.getComboBoxComponent("SaveAction")->getSelectedItemIndex(); | |||
if ( action > 0 ) { | |||
File destination = processor->activeFileCartridge; | |||
if ( ! destination.exists() ) { | |||
FileChooser fc("Destination Sysex", processor->dexedCartDir, "*.syx", 1); | |||
if ( action == 1 ) { | |||
FileChooser fc("Destination Sysex", processor->dexedCartDir, "*.syx;*.SYX", 1); | |||
if ( ! fc.browseForFileToSave(true) ) | |||
break; | |||
destination = fc.getResult(); | |||
} | |||
char sysexFile[4104]; | |||
exportSysexCart((char *) &sysexFile, (char *) &processor->sysex, 0); | |||
if ( ! destination.replaceWithData(sysexFile, 4104) ) { | |||
AlertWindow::showMessageBoxAsync(AlertWindow::WarningIcon, "Write error", "Unable to write file"); | |||
} | |||
processor->currentCart.saveVoice(destination); | |||
processor->activeFileCartridge = destination; | |||
} | |||
} else { | |||
packProgram((uint8_t *) &destSysex, (uint8_t *) processor->data, programNum, programName); | |||
char sysexFile[4104]; | |||
exportSysexCart((char *) &sysexFile, (char *) &destSysex, 0); | |||
if ( ! externalFile->replaceWithData(sysexFile, 4104) ) { | |||
destSysex.packProgram((uint8_t *) processor->data, programNum, programName, processor->controllers.opSwitch); | |||
if ( ! destSysex.saveVoice(*externalFile)) { | |||
AlertWindow::showMessageBoxAsync(AlertWindow::WarningIcon, "Write error", "Unable to write file"); | |||
} | |||
} | |||
@@ -1,6 +1,6 @@ | |||
/** | |||
* | |||
* Copyright (c) 2013-2015 Pascal Gauthier. | |||
* Copyright (c) 2013-2016 Pascal Gauthier. | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -31,11 +31,7 @@ | |||
//============================================================================== | |||
/** | |||
*/ | |||
class DexedAudioProcessorEditor : public AudioProcessorEditor, | |||
public ComboBoxListener, | |||
public Timer { | |||
PopupMenu cartPopup; | |||
class DexedAudioProcessorEditor : public AudioProcessorEditor, public ComboBoxListener, public Timer { | |||
MidiKeyboardComponent midiKeyboard; | |||
OperatorEditor operators[6]; | |||
Colour background; | |||
@@ -81,6 +81,10 @@ void PluginFx::init(int sr) { | |||
pCutoff = -1; | |||
pReso = -1; | |||
dc_r = 1.0-(126.0/sr); | |||
dc_id = 0; | |||
dc_od = 0; | |||
} | |||
inline float PluginFx::NR24(float sample,float g,float lpc) { | |||
@@ -97,6 +101,18 @@ inline float PluginFx::NR(float sample, float g) { | |||
} | |||
void PluginFx::process(float *work, int sampleSize) { | |||
// very basic DC filter | |||
float t_fd = work[0]; | |||
work[0] = work[0] - dc_id + dc_r * dc_od; | |||
dc_id = t_fd; | |||
for (int i=1; i<sampleSize; i++) { | |||
t_fd = work[i]; | |||
work[i] = work[i] - dc_id + dc_r * work[i-1]; | |||
dc_id = t_fd; | |||
} | |||
dc_od = work[sampleSize-1]; | |||
if ( uiGain != 1 ) { | |||
for(int i=0; i < sampleSize; i++ ) | |||
work[i] *= uiGain; | |||
@@ -55,6 +55,10 @@ class PluginFx { | |||
float rcor,rcorInv; | |||
int R; | |||
float dc_id; | |||
float dc_od; | |||
float dc_r; | |||
public: | |||
PluginFx(); | |||
@@ -1,6 +1,6 @@ | |||
/** | |||
* | |||
* Copyright (c) 2013-2015 Pascal Gauthier. | |||
* Copyright (c) 2013-2017 Pascal Gauthier. | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -49,8 +49,6 @@ public: | |||
String getValueDisplay() { | |||
String ret; | |||
int value = getValue(); | |||
if ( value == 48 ) | |||
value = 47; | |||
switch(value % 12) { | |||
case 0: ret << "C"; break; | |||
@@ -66,10 +64,128 @@ public: | |||
case 10: ret << "A#"; break; | |||
case 11: ret << "B"; break; | |||
} | |||
return ret << (value/12-2); | |||
return ret << (value/12+1); | |||
} | |||
}; | |||
class CtrlDXSwitch : public CtrlDX { | |||
public: | |||
CtrlDXSwitch(String name, int steps, int offset) : CtrlDX(name, steps, offset, 0) { | |||
}; | |||
String getValueDisplay() { | |||
return getValue() ? String("ON") : String("OFF"); | |||
} | |||
}; | |||
class CtrlDXOpMode : public CtrlDX { | |||
public: | |||
CtrlDXOpMode(String name, int steps, int offset) : CtrlDX(name, steps, offset, 0) { | |||
}; | |||
String getValueDisplay() { | |||
return getValue() ? String("FIXED") : String("RATIO"); | |||
} | |||
}; | |||
class CtrlDXBreakpoint : public CtrlDX { | |||
public: | |||
CtrlDXBreakpoint(String name, int steps, int offset) : CtrlDX(name, steps, offset, 0) { | |||
}; | |||
String getValueDisplay() { | |||
const char *breakNames[] = {"A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"}; | |||
String ret; | |||
ret << breakNames[getValue()%12] << (getValue()+9) / 12 - 1; | |||
return ret; | |||
} | |||
}; | |||
class CtrlTune : public Ctrl { | |||
public: | |||
DexedAudioProcessor *processor; | |||
CtrlTune(String name, DexedAudioProcessor *owner) : Ctrl(name) { | |||
processor = owner; | |||
} | |||
float getValueHost() { | |||
// meh. good enough for now | |||
int32_t tune = processor->controllers.masterTune / (1.0/12); | |||
tune = (tune >> 11) + 0x2000; | |||
return (float)tune / 0x4000; | |||
} | |||
void setValueHost(float v) { | |||
int32_t tune = (v * 0x4000) - 0x2000; | |||
processor->controllers.masterTune = ((float) (tune << 11)) * (1.0/12); | |||
} | |||
String getValueDisplay() { | |||
String display; | |||
display << (getValueHost() * 2) -1; | |||
return display; | |||
} | |||
void updateComponent() { | |||
if (slider != NULL) { | |||
slider->setValue(getValueHost(), dontSendNotification); | |||
} | |||
} | |||
}; | |||
class CtrlOpSwitch : public Ctrl { | |||
DexedAudioProcessor *processor; | |||
char *value; | |||
public : | |||
CtrlOpSwitch(String name, char *switchValue, DexedAudioProcessor *owner) : Ctrl(name) { | |||
processor = owner; | |||
value = switchValue; | |||
} | |||
void setValueHost(float f) { | |||
if ( f == 0 ) | |||
*value = '0'; | |||
else | |||
*value = '1'; | |||
updateDisplayName(); | |||
// the value is based on the controller | |||
parent->setDxValue(155, -1); | |||
} | |||
float getValueHost() { | |||
if ( *value == '0' ) | |||
return 0; | |||
else | |||
return 1; | |||
} | |||
String getValueDisplay() { | |||
String ret; | |||
ret << label << " " << (*value == '0' ? "OFF" : "ON"); | |||
return ret; | |||
} | |||
void updateComponent() { | |||
if (button != NULL) { | |||
if (*value == '0') { | |||
button->setToggleState(false, dontSendNotification); | |||
} else { | |||
button->setToggleState(true, dontSendNotification); | |||
} | |||
} | |||
} | |||
void updateDisplayName() { | |||
DexedAudioProcessorEditor *editor = (DexedAudioProcessorEditor *) parent->getActiveEditor(); | |||
if ( editor == NULL ) { | |||
return; | |||
} | |||
editor->global.setParamMessage(getValueDisplay()); | |||
editor->global.repaint(); | |||
} | |||
}; | |||
// ************************************************************************ | |||
// | |||
@@ -147,7 +263,7 @@ void Ctrl::updateDisplayName() { | |||
} | |||
// ************************************************************************ | |||
// CtrlDX - control DX mapping | |||
// CtrlFloat - control float values | |||
CtrlFloat::CtrlFloat(String name, float *storageValue) : Ctrl(name) { | |||
vPointer = storageValue; | |||
} | |||
@@ -182,21 +298,15 @@ CtrlDX::CtrlDX(String name, int steps, int offset, int displayValue) : Ctrl(name | |||
} | |||
float CtrlDX::getValueHost() { | |||
return dxValue / steps; | |||
return getValue() / (float) steps; | |||
} | |||
void CtrlDX::setValueHost(float f) { | |||
if ( f == 1 ) | |||
f = 0.999; | |||
setValue((f * steps)); | |||
setValue(roundToInt(f * steps)); | |||
} | |||
void CtrlDX::setValue(int v) { | |||
TRACE("setting value %d %d", dxOffset, v); | |||
if (v >= steps) { | |||
TRACE("WARNING: value too big %s : %d", label.toRawUTF8(), v); | |||
v = steps - 1; | |||
} | |||
dxValue = v; | |||
if (dxOffset >= 0) { | |||
if (parent != NULL) | |||
@@ -288,28 +398,31 @@ void DexedAudioProcessor::initCtrl() { | |||
output = new CtrlFloat("Output", &fx.uiGain); | |||
ctrl.add(output); | |||
algo = new CtrlDX("ALGORITHM", 32, 134, 1); | |||
tune = new CtrlTune("MASTER TUNE ADJ", this); | |||
ctrl.add(tune); | |||
algo = new CtrlDX("ALGORITHM", 31, 134, 1); | |||
ctrl.add(algo); | |||
feedback = new CtrlDX("FEEDBACK", 8, 135); | |||
feedback = new CtrlDX("FEEDBACK", 7, 135); | |||
ctrl.add(feedback); | |||
oscSync = new CtrlDX("OSC KEY SYNC", 2, 136); | |||
oscSync = new CtrlDXSwitch("OSC KEY SYNC", 1, 136); | |||
ctrl.add(oscSync); | |||
lfoRate = new CtrlDX("LFO SPEED", 100, 137); | |||
lfoRate = new CtrlDX("LFO SPEED", 99, 137); | |||
ctrl.add(lfoRate); | |||
lfoDelay = new CtrlDX("LFO DELAY", 100, 138); | |||
lfoDelay = new CtrlDX("LFO DELAY", 99, 138); | |||
ctrl.add(lfoDelay); | |||
lfoPitchDepth = new CtrlDX("LFO PM DEPTH", 100, 139); | |||
lfoPitchDepth = new CtrlDX("LFO PM DEPTH", 99, 139); | |||
ctrl.add(lfoPitchDepth); | |||
lfoAmpDepth = new CtrlDX("LFO AM DEPTH", 100, 140); | |||
lfoAmpDepth = new CtrlDX("LFO AM DEPTH", 99, 140); | |||
ctrl.add(lfoAmpDepth); | |||
lfoSync = new CtrlDX("LFO KEY SYNC", 2, 141); | |||
lfoSync = new CtrlDXSwitch("LFO KEY SYNC", 1, 141); | |||
ctrl.add(lfoSync); | |||
StringArray lbl; | |||
@@ -320,26 +433,26 @@ void DexedAudioProcessor::initCtrl() { | |||
lbl.add("SINE"); | |||
lbl.add("S&HOLD"); | |||
lfoWaveform = new CtrlDXLabel("LFO WAVE", 6, 142, lbl); | |||
lfoWaveform = new CtrlDXLabel("LFO WAVE", 5, 142, lbl); | |||
ctrl.add(lfoWaveform); | |||
transpose = new CtrlDXTranspose("MIDDLE C", 49, 144); | |||
transpose = new CtrlDXTranspose("MIDDLE C", 48, 144); | |||
ctrl.add(transpose); | |||
pitchModSens = new CtrlDX("P MODE SENS.", 8, 143); | |||
pitchModSens = new CtrlDX("P MODE SENS.", 7, 143); | |||
ctrl.add(pitchModSens); | |||
for (int i=0;i<4;i++) { | |||
String rate; | |||
rate << "PITCH EG RATE " << (i+1); | |||
pitchEgRate[i] = new CtrlDX(rate, 100, 126+i); | |||
pitchEgRate[i] = new CtrlDX(rate, 99, 126+i); | |||
ctrl.add(pitchEgRate[i]); | |||
} | |||
for (int i=0;i<4;i++) { | |||
String level; | |||
level << "PITCH EG LEVEL " << (i+1); | |||
pitchEgLevel[i] = new CtrlDX(level, 100, 130+i); | |||
pitchEgLevel[i] = new CtrlDX(level, 99, 130+i); | |||
ctrl.add(pitchEgLevel[i]); | |||
} | |||
@@ -360,81 +473,86 @@ void DexedAudioProcessor::initCtrl() { | |||
for (int j = 0; j < 4; j++) { | |||
String opRate; | |||
opRate << opName << " EG RATE " << (j + 1); | |||
opCtrl[opVal].egRate[j] = new CtrlDX(opRate, 100, opTarget + j); | |||
opCtrl[opVal].egRate[j] = new CtrlDX(opRate, 99, opTarget + j); | |||
ctrl.add(opCtrl[opVal].egRate[j]); | |||
} | |||
for (int j = 0; j < 4; j++) { | |||
String opLevel; | |||
opLevel << opName << " EG LEVEL " << (j + 1); | |||
opCtrl[opVal].egLevel[j] = new CtrlDX(opLevel, 100, opTarget + j + 4); | |||
opCtrl[opVal].egLevel[j] = new CtrlDX(opLevel, 99, opTarget + j + 4); | |||
ctrl.add(opCtrl[opVal].egLevel[j]); | |||
} | |||
String opVol; | |||
opVol << opName << " OUTPUT LEVEL"; | |||
opCtrl[opVal].level = new CtrlDX(opVol, 100, opTarget + 16); | |||
opCtrl[opVal].level = new CtrlDX(opVol, 99, opTarget + 16); | |||
ctrl.add(opCtrl[opVal].level); | |||
String opMode; | |||
opMode << opName << " MODE"; | |||
opCtrl[opVal].opMode = new CtrlDX(opMode, 2, opTarget + 17); | |||
opCtrl[opVal].opMode = new CtrlDXOpMode(opMode, 1, opTarget + 17); | |||
ctrl.add(opCtrl[opVal].opMode); | |||
String coarse; | |||
coarse << opName << " F COARSE"; | |||
opCtrl[opVal].coarse = new CtrlDX(coarse, 32, opTarget + 18); | |||
opCtrl[opVal].coarse = new CtrlDX(coarse, 31, opTarget + 18); | |||
ctrl.add(opCtrl[opVal].coarse); | |||
String fine; | |||
fine << opName << " F FINE"; | |||
opCtrl[opVal].fine = new CtrlDX(fine, 100, opTarget + 19); | |||
opCtrl[opVal].fine = new CtrlDX(fine, 99, opTarget + 19); | |||
ctrl.add(opCtrl[opVal].fine); | |||
String detune; | |||
detune << opName << " OSC DETUNE"; | |||
opCtrl[opVal].detune = new CtrlDX(detune, 15, opTarget + 20, -7); | |||
opCtrl[opVal].detune = new CtrlDX(detune, 14, opTarget + 20, -7); | |||
ctrl.add(opCtrl[opVal].detune); | |||
String sclBrkPt; | |||
sclBrkPt << opName << " BREAK POINT"; | |||
opCtrl[opVal].sclBrkPt = new CtrlDX(sclBrkPt, 100, opTarget + 8); | |||
opCtrl[opVal].sclBrkPt = new CtrlDXBreakpoint(sclBrkPt, 99, opTarget + 8); | |||
ctrl.add(opCtrl[opVal].sclBrkPt); | |||
String sclLeftDepth; | |||
sclLeftDepth << opName << " L SCALE DEPTH"; | |||
opCtrl[opVal].sclLeftDepth = new CtrlDX(sclLeftDepth, 100, opTarget + 9); | |||
opCtrl[opVal].sclLeftDepth = new CtrlDX(sclLeftDepth, 99, opTarget + 9); | |||
ctrl.add(opCtrl[opVal].sclLeftDepth); | |||
String sclRightDepth; | |||
sclRightDepth << opName << " R SCALE DEPTH"; | |||
opCtrl[opVal].sclRightDepth = new CtrlDX(sclRightDepth, 100, opTarget + 10); | |||
opCtrl[opVal].sclRightDepth = new CtrlDX(sclRightDepth, 99, opTarget + 10); | |||
ctrl.add(opCtrl[opVal].sclRightDepth); | |||
String sclLeftCurve; | |||
sclLeftCurve << opName << " L KEY SCALE"; | |||
opCtrl[opVal].sclLeftCurve = new CtrlDXLabel(sclLeftCurve, 4, opTarget + 11, keyScaleLabels); | |||
opCtrl[opVal].sclLeftCurve = new CtrlDXLabel(sclLeftCurve, 3, opTarget + 11, keyScaleLabels); | |||
ctrl.add(opCtrl[opVal].sclLeftCurve); | |||
String sclRightCurve; | |||
sclRightCurve << opName << " R KEY SCALE"; | |||
opCtrl[opVal].sclRightCurve = new CtrlDXLabel(sclRightCurve, 4, opTarget + 12, keyScaleLabels); | |||
opCtrl[opVal].sclRightCurve = new CtrlDXLabel(sclRightCurve, 3, opTarget + 12, keyScaleLabels); | |||
ctrl.add(opCtrl[opVal].sclRightCurve); | |||
String sclRate; | |||
sclRate << opName << " RATE SCALING"; | |||
opCtrl[opVal].sclRate = new CtrlDX(sclRate, 8, opTarget + 13); | |||
opCtrl[opVal].sclRate = new CtrlDX(sclRate, 7, opTarget + 13); | |||
ctrl.add(opCtrl[opVal].sclRate); | |||
String ampModSens; | |||
ampModSens << opName << " A MOD SENS."; | |||
opCtrl[opVal].ampModSens = new CtrlDX(ampModSens, 4, opTarget + 14); | |||
opCtrl[opVal].ampModSens = new CtrlDX(ampModSens, 3, opTarget + 14); | |||
ctrl.add(opCtrl[opVal].ampModSens); | |||
String velModSens; | |||
velModSens << opName << " KEY VELOCITY"; | |||
opCtrl[opVal].velModSens = new CtrlDX(velModSens, 8, opTarget + 15); | |||
opCtrl[opVal].velModSens = new CtrlDX(velModSens, 7, opTarget + 15); | |||
ctrl.add(opCtrl[opVal].velModSens); | |||
String opSwitchLabel; | |||
opSwitchLabel << opName << " SWITCH"; | |||
opCtrl[opVal].opSwitch = new CtrlOpSwitch(opSwitchLabel, (char *)&(controllers.opSwitch)+(5-i), this); | |||
ctrl.add(opCtrl[opVal].opSwitch); | |||
} | |||
for (int i=0; i < ctrl.size(); i++) { | |||
@@ -447,7 +565,12 @@ void DexedAudioProcessor::setDxValue(int offset, int v) { | |||
if (offset < 0) | |||
return; | |||
if ( data[offset] != v ) { | |||
if ( offset == 155 ) { | |||
// used on op switch that are not part of a Sysex packed cartridge, we render it | |||
// ourselves. | |||
packOpSwitch(); | |||
v = data[155]; | |||
} else if ( data[offset] != v ) { | |||
TRACE("setting dx %d %d", offset, v); | |||
data[offset] = v; | |||
} else { | |||
@@ -469,6 +592,7 @@ void DexedAudioProcessor::setDxValue(int offset, int v) { | |||
msg[4] = offset & 0x7F; | |||
if ( sysexComm.isOutputActive() ) { | |||
//TRACE("SENDING SYSEX: %.2X%.2X %.2X%.2X %.2X%.2X %.2X", msg[0], msg[1], msg[2], msg[3], msg[4], msg[5], msg[6]); | |||
sysexComm.send(MidiMessage(msg,7)); | |||
} | |||
} | |||
@@ -512,7 +636,8 @@ void DexedAudioProcessor::setCurrentProgram(int index) { | |||
panic(); | |||
index = index > 31 ? 31 : index; | |||
unpackProgram(index); | |||
currentCart.unpackProgram(data, index); | |||
strcpy(controllers.opSwitch, "111111"); | |||
lfo.reset(data + 137); | |||
currentProgram = index; | |||
triggerAsyncUpdate(); | |||
@@ -577,13 +702,31 @@ void DexedAudioProcessor::loadPreference() { | |||
sysexComm.setChl( prop.getIntValue( String("sysexChl") ) ); | |||
} | |||
if ( prop.containsKey( String("engineType" ) ) ) { | |||
engineType = prop.getIntValue( String("engineType") ); | |||
if ( prop.containsKey( String("engineType") ) ) { | |||
setEngineType(prop.getIntValue(String("engineType"))); | |||
} | |||
if ( prop.containsKey( String("showKeyboard") ) ) { | |||
showKeyboard = prop.getIntValue( String("showKeyboard") ); | |||
} | |||
if ( prop.containsKey( String("wheelMod") ) ) { | |||
controllers.wheel.parseConfig(prop.getValue(String("wheelMod")).toRawUTF8()); | |||
} | |||
if ( prop.containsKey( String("footMod") ) ) { | |||
controllers.foot.parseConfig(prop.getValue(String("footMod")).toRawUTF8()); | |||
} | |||
if ( prop.containsKey( String("breathMod") ) ) { | |||
controllers.breath.parseConfig(prop.getValue(String("breathMod")).toRawUTF8()); | |||
} | |||
if ( prop.containsKey( String("aftertouchMod") ) ) { | |||
controllers.at.parseConfig(prop.getValue(String("aftertouchMod")).toRawUTF8()); | |||
} | |||
controllers.refresh(); | |||
} | |||
void DexedAudioProcessor::savePreference() { | |||
@@ -601,9 +744,18 @@ void DexedAudioProcessor::savePreference() { | |||
prop.setValue(String("showKeyboard"), showKeyboard); | |||
//prop.setValue(String("engineResolution"), engineResolution); | |||
char mod_cfg[15]; | |||
controllers.wheel.setConfig(mod_cfg); | |||
prop.setValue(String("wheelMod"), mod_cfg); | |||
controllers.foot.setConfig(mod_cfg); | |||
prop.setValue(String("footMod"), mod_cfg); | |||
controllers.breath.setConfig(mod_cfg); | |||
prop.setValue(String("breathMod"), mod_cfg); | |||
controllers.at.setConfig(mod_cfg); | |||
prop.setValue(String("aftertouchMod"), mod_cfg); | |||
prop.setValue(String("engineType"), (int) engineType); | |||
prop.save(); | |||
} | |||
@@ -121,6 +121,7 @@ struct OperatorCtrl { | |||
ScopedPointer<CtrlDX> sclRate; | |||
ScopedPointer<CtrlDX> ampModSens; | |||
ScopedPointer<CtrlDX> velModSens; | |||
ScopedPointer<Ctrl> opSwitch; | |||
}; | |||
#endif // PLUGINPARAM_H_INCLUDED |
@@ -1,6 +1,6 @@ | |||
/** | |||
* | |||
* Copyright (c) 2013-2015 Pascal Gauthier. | |||
* Copyright (c) 2013-2017 Pascal Gauthier. | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -18,6 +18,9 @@ | |||
* | |||
*/ | |||
#include <stdarg.h> | |||
#include <bitset> | |||
#include "PluginProcessor.h" | |||
#include "PluginEditor.h" | |||
@@ -54,19 +57,23 @@ DexedAudioProcessor::DexedAudioProcessor() { | |||
resolvAppDir(); | |||
TRACE("controler %s", controllers.opSwitch); | |||
initCtrl(); | |||
sendSysexChange = true; | |||
normalizeDxVelocity = false; | |||
sysexComm.listener = this; | |||
showKeyboard = true; | |||
memset(&voiceStatus, 0, sizeof(VoiceStatus)); | |||
setEngineType(DEXED_ENGINE_MARKI); | |||
controllers.values_[kControllerPitchRange] = 3; | |||
controllers.values_[kControllerPitchStep] = 0; | |||
controllers.masterTune = 0; | |||
loadPreference(); | |||
setEngineType(DEXED_ENGINE_MODERN); | |||
for (int note = 0; note < MAX_ACTIVE_NOTES; ++note) { | |||
voices[note].dx7_note = NULL; | |||
} | |||
@@ -98,13 +105,18 @@ void DexedAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) | |||
currentNote = 0; | |||
controllers.values_[kControllerPitch] = 0x2000; | |||
controllers.values_[kControllerModWheel] = 0; | |||
controllers.modwheel_cc = 0; | |||
controllers.foot_cc = 0; | |||
controllers.breath_cc = 0; | |||
controllers.aftertouch_cc = 0; | |||
sustain = false; | |||
extra_buf_size = 0; | |||
keyboardState.reset(); | |||
lfo.reset(data + 137); | |||
nextMidi = new MidiMessage(0xF0); | |||
midiMsg = new MidiMessage(0xF0); | |||
} | |||
@@ -134,13 +146,13 @@ void DexedAudioProcessor::releaseResources() { | |||
} | |||
void DexedAudioProcessor::processBlock(AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { | |||
const int numSamples = buffer.getNumSamples(); | |||
int numSamples = buffer.getNumSamples(); | |||
int i; | |||
if ( refreshVoice ) { | |||
for(i=0;i < MAX_ACTIVE_NOTES;i++) { | |||
if ( voices[i].live ) | |||
voices[i].dx7_note->update(data, voices[i].midi_note); | |||
voices[i].dx7_note->update(data, voices[i].midi_note, voices[i].velocity); | |||
} | |||
lfo.reset(data + 137); | |||
refreshVoice = false; | |||
@@ -190,11 +202,11 @@ void DexedAudioProcessor::processBlock(AudioSampleBuffer& buffer, MidiBuffer& mi | |||
voices[note].dx7_note->compute(audiobuf.get(), lfovalue, lfodelay, &controllers); | |||
for (int j=0; j < N; ++j) { | |||
int32_t val = audiobuf.get()[j]; //& 0xFFFFF000); | |||
int32_t val = audiobuf.get()[j]; | |||
val = val >> 4; | |||
int clip_val = val < -(1 << 24) ? 0x8000 : val >= (1 << 24) ? 0x7fff : val >> 9; | |||
float f = ((float) clip_val) / (float) 32768; | |||
float f = ((float) clip_val) / (float) 0x8000; | |||
if( f > 1 ) f = 1; | |||
if( f < -1 ) f = -1; | |||
sumbuf[j] += f; | |||
@@ -231,11 +243,6 @@ void DexedAudioProcessor::processBlock(AudioSampleBuffer& buffer, MidiBuffer& mi | |||
else | |||
vuSignal = 0; | |||
} | |||
// DX7 is a mono synth | |||
for (int channel = 1; channel < getTotalNumOutputChannels(); ++channel) { | |||
buffer.copyFrom(channel, 0, channelData, numSamples, 1); | |||
} | |||
} | |||
@@ -266,36 +273,52 @@ void DexedAudioProcessor::processMidiMessage(const MidiMessage *msg) { | |||
case 0x90 : | |||
keydown(buf[1], buf[2]); | |||
return; | |||
case 0xb0 : { | |||
int controller = buf[1]; | |||
int ctrl = buf[1]; | |||
int value = buf[2]; | |||
// mod wheel | |||
if ( controller == 1 ) { | |||
controllers.values_[kControllerModWheel] = value; | |||
return; | |||
} | |||
// pedal | |||
if (controller == 64) { | |||
sustain = value != 0; | |||
if (!sustain) { | |||
for (int note = 0; note < MAX_ACTIVE_NOTES; note++) { | |||
if (voices[note].sustained && !voices[note].keydown) { | |||
voices[note].dx7_note->keyup(); | |||
voices[note].sustained = false; | |||
switch(ctrl) { | |||
case 1: | |||
controllers.modwheel_cc = value; | |||
controllers.refresh(); | |||
break; | |||
case 2: | |||
controllers.breath_cc = value; | |||
controllers.refresh(); | |||
break; | |||
case 4: | |||
controllers.foot_cc = value; | |||
controllers.refresh(); | |||
break; | |||
case 64: | |||
sustain = value > 63; | |||
if (!sustain) { | |||
for (int note = 0; note < MAX_ACTIVE_NOTES; note++) { | |||
if (voices[note].sustained && !voices[note].keydown) { | |||
voices[note].dx7_note->keyup(); | |||
voices[note].sustained = false; | |||
} | |||
} | |||
} | |||
} | |||
return; | |||
break; | |||
case 123: | |||
panic(); | |||
break; | |||
} | |||
} | |||
return; | |||
case 0xc0 : | |||
setCurrentProgram(buf[1]); | |||
return; | |||
return; | |||
// aftertouch | |||
case 0xd0 : | |||
controllers.aftertouch_cc = buf[1]; | |||
controllers.refresh(); | |||
return; | |||
} | |||
switch (cmd) { | |||
@@ -303,7 +326,6 @@ void DexedAudioProcessor::processMidiMessage(const MidiMessage *msg) { | |||
controllers.values_[kControllerPitch] = buf[1] | (buf[2] << 7); | |||
break; | |||
} | |||
} | |||
void DexedAudioProcessor::keydown(uint8_t pitch, uint8_t velo) { | |||
@@ -324,6 +346,7 @@ void DexedAudioProcessor::keydown(uint8_t pitch, uint8_t velo) { | |||
currentNote = (note + 1) % MAX_ACTIVE_NOTES; | |||
lfo.keydown(); // TODO: should only do this if # keys down was 0 | |||
voices[note].midi_note = pitch; | |||
voices[note].velocity = velo; | |||
voices[note].sustained = sustain; | |||
voices[note].keydown = true; | |||
voices[note].dx7_note->init(data, pitch, velo); | |||
@@ -414,45 +437,72 @@ void DexedAudioProcessor::handleIncomingMidiMessage(MidiInput* source, const Mid | |||
sysexComm.inActivity = true; | |||
if ( ! message.isSysEx() ) | |||
return; | |||
//const uint8 *buf = msg->getSysExData(); | |||
const uint8 *buf = message.getRawData(); | |||
int sz = message.getRawDataSize(); | |||
if ( sz < 3 ) | |||
return; | |||
TRACE("SYSEX RECEIVED %d", sz); | |||
//TRACE("%X %X %X %X %X %X", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); | |||
if ( ! message.isSysEx() ) | |||
return; | |||
// test if it is a Yamaha Sysex | |||
if ( buf[1] != 0x43 ) { | |||
TRACE("not a yamaha sysex %d", buf[0]); | |||
return; | |||
} | |||
int substatus = buf[2] >> 4; | |||
if ( substatus == 0 ) { | |||
// single voice dump | |||
if ( buf[3] == 0 ) { | |||
if ( sz < 156 ) { | |||
TRACE("wrong single voice datasize %d", sz); | |||
return; | |||
} | |||
updateProgramFromSysex(buf+6); | |||
} | |||
// single voice dump | |||
if ( buf[3] == 0 ) { | |||
if ( sz < 155 ) { | |||
TRACE("wrong single voice datasize %d", sz); | |||
return; | |||
// 32 voice dump | |||
if ( buf[3] == 9 ) { | |||
if ( sz < 4104 ) { | |||
TRACE("wrong 32 voice dump data size %d", sz); | |||
return; | |||
} | |||
Cartridge received; | |||
if ( received.load(buf, sz) == 0 ) { | |||
loadCartridge(received); | |||
setCurrentProgram(0); | |||
} | |||
} | |||
updateProgramFromSysex(buf+6); | |||
} | |||
// 32 voice dump | |||
if ( buf[3] == 9 ) { | |||
if ( sz < 4104 ) { | |||
TRACE("wrong 32 voice datasize %d", sz); | |||
} else if ( substatus == 1 ) { | |||
// parameter change | |||
if ( sz < 7 ) { | |||
TRACE("wrong single voice datasize %d", sz); | |||
return; | |||
} | |||
uint8 offset = (buf[3] << 7) + buf[4]; | |||
uint8 value = buf[5]; | |||
TRACE("parameter change message offset:%d value:%d", offset, value); | |||
if ( offset > 155 ) { | |||
TRACE("wrong offset size"); | |||
return; | |||
} | |||
TRACE("update 32bulk voice"); | |||
importSysex((const char *)buf); | |||
setCurrentProgram(0); | |||
if ( offset == 155 ) { | |||
unpackOpSwitch(value); | |||
} else { | |||
data[offset] = value; | |||
} | |||
} else { | |||
TRACE("unknown sysex substatus: %d", substatus); | |||
} | |||
updateHostDisplay(); | |||
forceRefreshUI = true; | |||
} | |||
@@ -462,18 +512,18 @@ int DexedAudioProcessor::getEngineType() { | |||
} | |||
void DexedAudioProcessor::setEngineType(int tp) { | |||
TRACE("settings engine %d", tp); | |||
switch (tp) { | |||
case DEXED_ENGINE_MODERN : | |||
controllers.core = &engineMsfa; | |||
break; | |||
case DEXED_ENGINE_MARKI: | |||
controllers.sinBitFilter = 0xFFFFC000; // 10 bit | |||
controllers.dacBitFilter = 0xFFFFF000; // semi 14 bit | |||
controllers.core = &engineMkI; | |||
break; | |||
case DEXED_ENGINE_OPL: | |||
controllers.core = &engineOpl; | |||
break; | |||
default: | |||
controllers.core = &engineMsfa; | |||
break; | |||
} | |||
engineType = tp; | |||
} | |||
@@ -581,3 +631,15 @@ AudioProcessorEditor* DexedAudioProcessor::createEditor() { | |||
void DexedAudioProcessor::handleAsyncUpdate() { | |||
updateUI(); | |||
} | |||
void dexed_trace(const char *source, const char *fmt, ...) { | |||
char output[4096]; | |||
va_list argptr; | |||
va_start(argptr, fmt); | |||
vsnprintf(output, 4095, fmt, argptr); | |||
va_end(argptr); | |||
String dest; | |||
dest << source << " " << output; | |||
Logger::writeToLog(dest); | |||
} |
@@ -1,6 +1,6 @@ | |||
/** | |||
* | |||
* Copyright (c) 2013-2015 Pascal Gauthier. | |||
* Copyright (c) 2013-2017 Pascal Gauthier. | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -37,6 +37,7 @@ | |||
struct ProcessorVoice { | |||
int midi_note; | |||
int velocity; | |||
bool keydown; | |||
bool sustained; | |||
bool live; | |||
@@ -115,14 +116,17 @@ class DexedAudioProcessor : public AudioProcessor, public AsyncUpdater, public | |||
char clipboardContent; | |||
void resolvAppDir(); | |||
void unpackOpSwitch(char packOpValue); | |||
void packOpSwitch(); | |||
public : | |||
// in MIDI units (0x4000 is neutral) | |||
Controllers controllers; | |||
StringArray programNames; | |||
char sysex[4096]; | |||
char data[161]; | |||
StringArray programNames; | |||
Cartridge currentCart; | |||
uint8_t data[161]; | |||
//CartridgeManager cartManager; | |||
SysexComm sysexComm; | |||
VoiceStatus voiceStatus; | |||
File activeFileCartridge; | |||
@@ -153,8 +157,9 @@ public : | |||
ScopedPointer<CtrlFloat> fxCutoff; | |||
ScopedPointer<CtrlFloat> fxReso; | |||
ScopedPointer<CtrlFloat> output; | |||
ScopedPointer<Ctrl> tune; | |||
int importSysex(const char *imported); | |||
void loadCartridge(Cartridge &cart); | |||
void setDxValue(int offset, int v); | |||
//============================================================================== | |||
@@ -184,7 +189,6 @@ public : | |||
bool hasEditor() const; | |||
void updateUI(); | |||
bool peekVoiceStatus(); | |||
void unpackProgram(int idx); | |||
void updateProgramFromSysex(const uint8 *rawdata); | |||
void setupStartupCart(); | |||
@@ -29,7 +29,9 @@ ProgramListBox::ProgramListBox(const String name, int numCols) : Component(name) | |||
selectedPgm = -1; | |||
hasContent = false; | |||
dragCandidate = -1; | |||
pgmCandidate = -1; | |||
readOnly = false; | |||
programNames.clear(); | |||
} | |||
void ProgramListBox::paint(Graphics &g) { | |||
@@ -85,9 +87,9 @@ void ProgramListBox::resized() { | |||
cellHeight = getHeight() / rows; | |||
} | |||
void ProgramListBox::setCartridge(char *sysex) { | |||
extractProgramNames((const char *)sysex, programNames); | |||
memcpy(cartContent, sysex, 4104); | |||
void ProgramListBox::setCartridge(Cartridge &cart) { | |||
cartContent = cart; | |||
cartContent.getProgramNames(programNames); | |||
hasContent = true; | |||
repaint(); | |||
} | |||
@@ -100,29 +102,36 @@ int ProgramListBox::programPosition(int x, int y) { | |||
return (y / cellHeight) + ((x / cellWidth) * rows); | |||
} | |||
void ProgramListBox::mouseDoubleClick(const MouseEvent &event) { | |||
void ProgramListBox::mouseDown(const MouseEvent &event) { | |||
pgmCandidate = -1; | |||
if ( ! hasContent ) | |||
return; | |||
if ( ! event.mods.isLeftButtonDown() ) | |||
return; | |||
int pos = programPosition(event.getMouseDownX(), event.getMouseDownY()); | |||
if ( listener != nullptr ) | |||
listener->programSelected(this, pos); | |||
if ( event.mods.isRightButtonDown() || event.mods.isAnyModifierKeyDown() ) { | |||
int pos = programPosition(event.getMouseDownX(), event.getMouseDownY()); | |||
if ( listener != nullptr ) | |||
listener->programRightClicked(this, pos); | |||
return; | |||
} | |||
pgmCandidate = programPosition(event.getMouseDownX(), event.getMouseDownY()); | |||
} | |||
void ProgramListBox::mouseDown(const MouseEvent &event) { | |||
if ( ! hasContent ) | |||
return; | |||
if ( ! event.mods.isRightButtonDown() ) | |||
void ProgramListBox::mouseUp(const MouseEvent &event) { | |||
if ( pgmCandidate == -1 ) | |||
return; | |||
int pos = programPosition(event.getMouseDownX(), event.getMouseDownY()); | |||
if ( listener != nullptr ) | |||
listener->programRightClicked(this, pos); | |||
if ( pgmCandidate == pos) { | |||
if ( listener != nullptr ) | |||
listener->programSelected(this, pgmCandidate); | |||
pgmCandidate = -1; | |||
} | |||
} | |||
void ProgramListBox::mouseDrag(const MouseEvent &event) { | |||
pgmCandidate = -1; | |||
if ( ! hasContent ) | |||
return; | |||
if ( dragCandidate != -1 ) | |||
@@ -138,7 +147,7 @@ void ProgramListBox::mouseDrag(const MouseEvent &event) { | |||
g.fillRect(0,0,cellWidth, cellHeight); | |||
g.setColour(Colours::white); | |||
g.drawFittedText(programNames[position], 0, 0, cellWidth, cellHeight, Justification::centred, true); | |||
void *src = cartContent + (position*128); | |||
void *src = cartContent.getRawVoice() + (position*128); | |||
var description = var(src, 128); | |||
dragContainer->startDragging(description, this, snapshot, false); | |||
} | |||
@@ -148,9 +157,7 @@ void ProgramListBox::setSelected(int idx) { | |||
selectedPgm = idx; | |||
} | |||
char* ProgramListBox::getCurrentCart() { | |||
if ( ! hasContent ) | |||
return nullptr; | |||
Cartridge &ProgramListBox::getCurrentCart() { | |||
return cartContent; | |||
} | |||
@@ -22,6 +22,7 @@ | |||
#define PROGRAMLISTBOX_H_INCLUDED | |||
#include "JuceHeader.h" | |||
#include "PluginData.h" | |||
class ProgramListBox; | |||
class ProgramListBoxListener { | |||
@@ -41,10 +42,10 @@ class ProgramListBox : public Component, public DragAndDropTarget { | |||
int programPosition(int x, int y); | |||
int selectedPgm; | |||
// TODO: this should be a pointer | |||
char cartContent[4104]; | |||
Cartridge cartContent; | |||
int dragCandidate; | |||
int pgmCandidate; | |||
public: | |||
StringArray programNames; | |||
@@ -52,14 +53,15 @@ public: | |||
ProgramListBox(const String name, int numCols); | |||
void addListener(ProgramListBoxListener *listener); | |||
void paint(Graphics &g); | |||
void setCartridge(char *sysex); | |||
void resized(); | |||
void mouseDoubleClick(const MouseEvent &event); | |||
void mouseDown(const MouseEvent &event); | |||
void mouseDrag(const MouseEvent &event); | |||
void paint(Graphics &g) override; | |||
void resized() override; | |||
void mouseDown(const MouseEvent &event) override; | |||
void mouseDrag(const MouseEvent &event) override; | |||
void mouseUp(const MouseEvent &event) override; | |||
void setSelected(int idx); | |||
char* getCurrentCart(); | |||
Cartridge &getCurrentCart(); | |||
void setCartridge(Cartridge &cart); | |||
bool isInterestedInDragSource(const SourceDetails& dragSourceDetails) override; | |||
void itemDragEnter(const SourceDetails &dragSourceDetails) override; | |||
@@ -142,9 +142,6 @@ void SysexComm::setChl(int chl) { | |||
int SysexComm::send(const MidiMessage &message) { | |||
if ( output == NULL ) | |||
return 2; | |||
TRACE("send sysex"); | |||
outActivity = true; | |||
output->sendMessageNow(message); | |||
return 0; | |||
@@ -1,12 +1,12 @@ | |||
/* | |||
* Copyright 2013 Google Inc. | |||
* | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
@@ -18,25 +18,108 @@ | |||
#define __CONTROLLERS_H | |||
#include "synth.h" | |||
#include "../Dexed.h" | |||
#include <stdio.h> | |||
#include <string.h> | |||
#ifdef _WIN32 | |||
#define snprintf _snprintf | |||
#endif | |||
// State of MIDI controllers | |||
const int kControllerModWheel = 1; | |||
const int kControllerPitch = 128; | |||
const int kControllerPitchRange = 129; | |||
const int kControllerPitchStep = 130; | |||
class FmCore; | |||
struct FmMod { | |||
int range; | |||
bool pitch; | |||
bool amp; | |||
bool eg; | |||
FmMod() { | |||
range = 0; | |||
pitch = false; | |||
amp = false; | |||
eg = false; | |||
} | |||
void parseConfig(const char *cfg) { | |||
int r = 0, p = 0, a = 0, e = 0; | |||
sscanf(cfg, "%d %d %d %d", &r, &p, &a, &e); | |||
range = r < 0 && r > 127 ? 0 : r; | |||
pitch = p != 0; | |||
amp = a != 0; | |||
eg = e != 0; | |||
} | |||
void setConfig(char *cfg) { | |||
snprintf(cfg, 13, "%d %d %d %d", range, pitch, amp, eg); | |||
} | |||
}; | |||
class Controllers { | |||
public: | |||
int values_[131]; | |||
void applyMod(int cc, FmMod &mod) { | |||
float range = 0.01 * mod.range; | |||
int total = cc * range; | |||
if ( mod.amp ) | |||
amp_mod = max(amp_mod, total); | |||
if ( mod.pitch ) | |||
pitch_mod = max(pitch_mod, total); | |||
if ( mod.eg ) | |||
eg_mod = max(eg_mod, total); | |||
} | |||
public: | |||
int values_[131]; | |||
// engine bit filters | |||
uint32_t sinBitFilter; // bit filter based upon sin LUT lookup | |||
uint32_t dacBitFilter; // bit filter based upon DAC resolution | |||
uint32_t mulBitFilter; // bit filter based upon multipliers (?????) | |||
char opSwitch[7]; | |||
int amp_mod; | |||
int pitch_mod; | |||
int eg_mod; | |||
int aftertouch_cc; | |||
int breath_cc; | |||
int foot_cc; | |||
int modwheel_cc; | |||
int masterTune; | |||
FmMod wheel; | |||
FmMod foot; | |||
FmMod breath; | |||
FmMod at; | |||
Controllers() { | |||
amp_mod = 0; | |||
pitch_mod = 0; | |||
eg_mod = 0; | |||
strcpy(opSwitch, "111111"); | |||
} | |||
void refresh() { | |||
amp_mod = 0; | |||
pitch_mod = 0; | |||
eg_mod = 0; | |||
applyMod(modwheel_cc, wheel); | |||
applyMod(breath_cc, breath); | |||
applyMod(foot_cc, foot); | |||
applyMod(aftertouch_cc, at); | |||
if ( ! ((wheel.eg || foot.eg) || (breath.eg || at.eg)) ) | |||
eg_mod = 127; | |||
TRACE("amp_mod %d pitch_mod %d", amp_mod, pitch_mod); | |||
} | |||
FmCore *core; | |||
FmCore *core; | |||
}; | |||
#endif // __CONTROLLERS_H | |||
@@ -1,12 +1,13 @@ | |||
/* | |||
* Copyright 2016-2017 Pascal Gauthier. | |||
* Copyright 2012 Google Inc. | |||
* | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
@@ -14,10 +15,6 @@ | |||
* limitations under the License. | |||
*/ | |||
#ifdef VERBOSE | |||
#include <iostream> | |||
using namespace std; | |||
#endif | |||
#include <math.h> | |||
#include <stdlib.h> | |||
#include "synth.h" | |||
@@ -26,105 +23,107 @@ using namespace std; | |||
#include "controllers.h" | |||
#include "dx7note.h" | |||
const int FEEDBACK_BITDEPTH = 8; | |||
int32_t midinote_to_logfreq(int midinote) { | |||
const int base = 50857777; // (1 << 24) * (log(440) / log(2) - 69/12) | |||
const int step = (1 << 24) / 12; | |||
return base + step * midinote; | |||
const int base = 50857777; // (1 << 24) * (log(440) / log(2) - 69/12) | |||
const int step = (1 << 24) / 12; | |||
return base + step * midinote; | |||
} | |||
const int32_t coarsemul[] = { | |||
-16777216, 0, 16777216, 26591258, 33554432, 38955489, 43368474, 47099600, | |||
50331648, 53182516, 55732705, 58039632, 60145690, 62083076, 63876816, | |||
65546747, 67108864, 68576247, 69959732, 71268397, 72509921, 73690858, | |||
74816848, 75892776, 76922906, 77910978, 78860292, 79773775, 80654032, | |||
81503396, 82323963, 83117622 | |||
-16777216, 0, 16777216, 26591258, 33554432, 38955489, 43368474, 47099600, | |||
50331648, 53182516, 55732705, 58039632, 60145690, 62083076, 63876816, | |||
65546747, 67108864, 68576247, 69959732, 71268397, 72509921, 73690858, | |||
74816848, 75892776, 76922906, 77910978, 78860292, 79773775, 80654032, | |||
81503396, 82323963, 83117622 | |||
}; | |||
int32_t osc_freq(int midinote, int mode, int coarse, int fine, int detune) { | |||
// TODO: pitch randomization | |||
int32_t logfreq; | |||
if (mode == 0) { | |||
logfreq = midinote_to_logfreq(midinote); | |||
logfreq += coarsemul[coarse & 31]; | |||
if (fine) { | |||
// (1 << 24) / log(2) | |||
logfreq += (int32_t)floor(24204406.323123 * log(1 + 0.01 * fine) + 0.5); | |||
// TODO: pitch randomization | |||
int32_t logfreq; | |||
if (mode == 0) { | |||
logfreq = midinote_to_logfreq(midinote); | |||
logfreq += coarsemul[coarse & 31]; | |||
if (fine) { | |||
// (1 << 24) / log(2) | |||
logfreq += (int32_t)floor(24204406.323123 * log(1 + 0.01 * fine) + 0.5); | |||
} | |||
// This was measured at 7.213Hz per count at 9600Hz, but the exact | |||
// value is somewhat dependent on midinote. Close enough for now. | |||
logfreq += 12606 * (detune - 7); | |||
} else { | |||
// ((1 << 24) * log(10) / log(2) * .01) << 3 | |||
logfreq = (4458616 * ((coarse & 3) * 100 + fine)) >> 3; | |||
logfreq += detune > 7 ? 13457 * (detune - 7) : 0; | |||
} | |||
// This was measured at 7.213Hz per count at 9600Hz, but the exact | |||
// value is somewhat dependent on midinote. Close enough for now. | |||
logfreq += 12606 * (detune - 7); | |||
} else { | |||
// ((1 << 24) * log(10) / log(2) * .01) << 3 | |||
logfreq = (4458616 * ((coarse & 3) * 100 + fine)) >> 3; | |||
logfreq += detune > 7 ? 13457 * (detune - 7) : 0; | |||
} | |||
return logfreq; | |||
return logfreq; | |||
} | |||
const uint8_t velocity_data[64] = { | |||
0, 70, 86, 97, 106, 114, 121, 126, 132, 138, 142, 148, 152, 156, 160, 163, | |||
166, 170, 173, 174, 178, 181, 184, 186, 189, 190, 194, 196, 198, 200, 202, | |||
205, 206, 209, 211, 214, 216, 218, 220, 222, 224, 225, 227, 229, 230, 232, | |||
233, 235, 237, 238, 240, 241, 242, 243, 244, 246, 246, 248, 249, 250, 251, | |||
252, 253, 254 | |||
0, 70, 86, 97, 106, 114, 121, 126, 132, 138, 142, 148, 152, 156, 160, 163, | |||
166, 170, 173, 174, 178, 181, 184, 186, 189, 190, 194, 196, 198, 200, 202, | |||
205, 206, 209, 211, 214, 216, 218, 220, 222, 224, 225, 227, 229, 230, 232, | |||
233, 235, 237, 238, 240, 241, 242, 243, 244, 246, 246, 248, 249, 250, 251, | |||
252, 253, 254 | |||
}; | |||
// See "velocity" section of notes. Returns velocity delta in microsteps. | |||
int ScaleVelocity(int velocity, int sensitivity) { | |||
int clamped_vel = max(0, min(127, velocity)); | |||
int vel_value = velocity_data[clamped_vel >> 1] - 239; | |||
int scaled_vel = ((sensitivity * vel_value + 7) >> 3) << 4; | |||
return scaled_vel; | |||
int clamped_vel = max(0, min(127, velocity)); | |||
int vel_value = velocity_data[clamped_vel >> 1] - 239; | |||
int scaled_vel = ((sensitivity * vel_value + 7) >> 3) << 4; | |||
return scaled_vel; | |||
} | |||
int ScaleRate(int midinote, int sensitivity) { | |||
int x = min(31, max(0, midinote / 3 - 7)); | |||
int qratedelta = (sensitivity * x) >> 3; | |||
int x = min(31, max(0, midinote / 3 - 7)); | |||
int qratedelta = (sensitivity * x) >> 3; | |||
#ifdef SUPER_PRECISE | |||
int rem = x & 7; | |||
if (sensitivity == 3 && rem == 3) { | |||
qratedelta -= 1; | |||
} else if (sensitivity == 7 && rem > 0 && rem < 4) { | |||
qratedelta += 1; | |||
} | |||
int rem = x & 7; | |||
if (sensitivity == 3 && rem == 3) { | |||
qratedelta -= 1; | |||
} else if (sensitivity == 7 && rem > 0 && rem < 4) { | |||
qratedelta += 1; | |||
} | |||
#endif | |||
return qratedelta; | |||
return qratedelta; | |||
} | |||
const uint8_t exp_scale_data[] = { | |||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 14, 16, 19, 23, 27, 33, 39, 47, 56, 66, | |||
80, 94, 110, 126, 142, 158, 174, 190, 206, 222, 238, 250 | |||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 14, 16, 19, 23, 27, 33, 39, 47, 56, 66, | |||
80, 94, 110, 126, 142, 158, 174, 190, 206, 222, 238, 250 | |||
}; | |||
int ScaleCurve(int group, int depth, int curve) { | |||
int scale; | |||
if (curve == 0 || curve == 3) { | |||
// linear | |||
scale = (group * depth * 329) >> 12; | |||
} else { | |||
// exponential | |||
int n_scale_data = sizeof(exp_scale_data); | |||
int raw_exp = exp_scale_data[min(group, n_scale_data - 1)]; | |||
scale = (raw_exp * depth * 329) >> 15; | |||
} | |||
if (curve < 2) { | |||
scale = -scale; | |||
} | |||
return scale; | |||
int scale; | |||
if (curve == 0 || curve == 3) { | |||
// linear | |||
scale = (group * depth * 329) >> 12; | |||
} else { | |||
// exponential | |||
int n_scale_data = sizeof(exp_scale_data); | |||
int raw_exp = exp_scale_data[min(group, n_scale_data - 1)]; | |||
scale = (raw_exp * depth * 329) >> 15; | |||
} | |||
if (curve < 2) { | |||
scale = -scale; | |||
} | |||
return scale; | |||
} | |||
int ScaleLevel(int midinote, int break_pt, int left_depth, int right_depth, | |||
int left_curve, int right_curve) { | |||
int offset = midinote - break_pt - 17; | |||
if (offset >= 0) { | |||
return ScaleCurve(offset / 3, right_depth, right_curve); | |||
} else { | |||
return ScaleCurve((-offset) / 3, left_depth, left_curve); | |||
} | |||
int left_curve, int right_curve) { | |||
int offset = midinote - break_pt - 17; | |||
if (offset >= 0) { | |||
return ScaleCurve((offset+1) / 3, right_depth, right_curve); | |||
} else { | |||
return ScaleCurve(-(offset-1) / 3, left_depth, left_curve); | |||
} | |||
} | |||
static const uint8_t pitchmodsenstab[] = { | |||
0, 10, 20, 33, 55, 92, 153, 255 | |||
0, 10, 20, 33, 55, 92, 153, 255 | |||
}; | |||
// 0, 66, 109, 255 | |||
@@ -139,75 +138,62 @@ Dx7Note::Dx7Note() { | |||
} | |||
} | |||
void Dx7Note::init(const char patch[156], int midinote, int velocity) { | |||
int rates[4]; | |||
int levels[4]; | |||
for (int op = 0; op < 6; op++) { | |||
int off = op * 21; | |||
for (int i = 0; i < 4; i++) { | |||
rates[i] = patch[off + i]; | |||
levels[i] = patch[off + 4 + i]; | |||
void Dx7Note::init(const uint8_t patch[156], int midinote, int velocity) { | |||
int rates[4]; | |||
int levels[4]; | |||
for (int op = 0; op < 6; op++) { | |||
int off = op * 21; | |||
for (int i = 0; i < 4; i++) { | |||
rates[i] = patch[off + i]; | |||
levels[i] = patch[off + 4 + i]; | |||
} | |||
int outlevel = patch[off + 16]; | |||
outlevel = Env::scaleoutlevel(outlevel); | |||
int level_scaling = ScaleLevel(midinote, patch[off + 8], patch[off + 9], | |||
patch[off + 10], patch[off + 11], patch[off + 12]); | |||
outlevel += level_scaling; | |||
outlevel = min(127, outlevel); | |||
outlevel = outlevel << 5; | |||
outlevel += ScaleVelocity(velocity, patch[off + 15]); | |||
outlevel = max(0, outlevel); | |||
int rate_scaling = ScaleRate(midinote, patch[off + 13]); | |||
env_[op].init(rates, levels, outlevel, rate_scaling); | |||
int mode = patch[off + 17]; | |||
int coarse = patch[off + 18]; | |||
int fine = patch[off + 19]; | |||
int detune = patch[off + 20]; | |||
int32_t freq = osc_freq(midinote, mode, coarse, fine, detune); | |||
basepitch_[op] = freq; | |||
ampmodsens_[op] = ampmodsenstab[patch[off + 14] & 3]; | |||
} | |||
int outlevel = patch[off + 16]; | |||
outlevel = Env::scaleoutlevel(outlevel); | |||
#ifdef VERBOSE | |||
for (int j = 8; j < 12; j++) { | |||
cout << (int)patch[off + j] << " "; | |||
for (int i = 0; i < 4; i++) { | |||
rates[i] = patch[126 + i]; | |||
levels[i] = patch[130 + i]; | |||
} | |||
#endif | |||
int level_scaling = ScaleLevel(midinote, patch[off + 8], patch[off + 9], | |||
patch[off + 10], patch[off + 11], patch[off + 12]); | |||
outlevel += level_scaling; | |||
outlevel = min(127, outlevel); | |||
#ifdef VERBOSE | |||
cout << op << ": " << level_scaling << " " << outlevel << endl; | |||
#endif | |||
outlevel = outlevel << 5; | |||
outlevel += ScaleVelocity(velocity, patch[off + 15]); | |||
outlevel = max(0, outlevel); | |||
int rate_scaling = ScaleRate(midinote, patch[off + 13]); | |||
env_[op].init(rates, levels, outlevel, rate_scaling); | |||
int mode = patch[off + 17]; | |||
int coarse = patch[off + 18]; | |||
int fine = patch[off + 19]; | |||
int detune = patch[off + 20]; | |||
int32_t freq = osc_freq(midinote, mode, coarse, fine, detune); | |||
basepitch_[op] = freq; | |||
// cout << op << " freq: " << freq << endl; | |||
//params_[op].phase = 0; | |||
//params_[op].gain_out = 0; | |||
ampmodsens_[op] = ampmodsenstab[patch[off + 14] & 3]; | |||
} | |||
for (int i = 0; i < 4; i++) { | |||
rates[i] = patch[126 + i]; | |||
levels[i] = patch[130 + i]; | |||
} | |||
pitchenv_.set(rates, levels); | |||
algorithm_ = patch[134]; | |||
int feedback = patch[135]; | |||
fb_shift_ = feedback != 0 ? 8 - feedback : 16; | |||
pitchmoddepth_ = (patch[139] * 165) >> 6; | |||
pitchmodsens_ = pitchmodsenstab[patch[143] & 7]; | |||
ampmoddepth_ = (patch[140] * 165) >> 6; | |||
pitchenv_.set(rates, levels); | |||
algorithm_ = patch[134]; | |||
int feedback = patch[135]; | |||
fb_shift_ = feedback != 0 ? FEEDBACK_BITDEPTH - feedback : 16; | |||
pitchmoddepth_ = (patch[139] * 165) >> 6; | |||
pitchmodsens_ = pitchmodsenstab[patch[143] & 7]; | |||
ampmoddepth_ = (patch[140] * 165) >> 6; | |||
} | |||
void Dx7Note::compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Controllers *ctrls) { | |||
int32_t pitchmod = pitchenv_.getsample(); | |||
// ==== PITCH ==== | |||
uint32_t pmd = pitchmoddepth_ * lfo_delay; // Q32 | |||
// TODO(PG) : make this integer friendly | |||
uint32_t pwmd = (ctrls->values_[kControllerModWheel] * 0.7874) * (1 << 24); | |||
int32_t senslfo = pitchmodsens_ * (lfo_val - (1 << 23)); | |||
int32_t pmod_1 = (((int64_t) pmd) * (int64_t) senslfo) >> 39; | |||
pmod_1 = abs(pmod_1); | |||
int32_t pmod_2 = ((int64_t)ctrls->pitch_mod * (int64_t)senslfo) >> 14; | |||
pmod_2 = abs(pmod_2); | |||
int32_t pitch_mod = max(pmod_1, pmod_2); | |||
pitch_mod = pitchenv_.getsample() + (pitch_mod * (senslfo < 0 ? -1 : 1)); | |||
uint32_t amd = ((int64_t) ampmoddepth_ * (int64_t) lfo_delay) >> 8; // Q24 :D | |||
amd = ((int64_t) amd * (int64_t) lfo_val) >> 24; | |||
pitchmod += (((int64_t) pwmd) * (int64_t) senslfo) >> 39; | |||
pitchmod += (((int64_t) pmd) * (int64_t) senslfo) >> 39; | |||
// ---- PITCH BEND ---- | |||
int pitchbend = ctrls->values_[kControllerPitch]; | |||
int32_t pb = (pitchbend - 0x2000); | |||
if (pb != 0) { | |||
if (ctrls->values_[kControllerPitchStep] == 0) { | |||
pb = ((float) (pb << 11)) * ((float) ctrls->values_[kControllerPitchRange]) / 12.0; | |||
@@ -217,57 +203,92 @@ void Dx7Note::compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Co | |||
pb = (pb * (8191 / stp)) << 11; | |||
} | |||
} | |||
pitchmod += pb; | |||
pitch_mod += pb; | |||
pitch_mod += ctrls->masterTune; | |||
// ==== AMP MOD ==== | |||
uint32_t amod_1 = ((int64_t) ampmoddepth_ * (int64_t) lfo_delay) >> 8; // Q24 :D | |||
amod_1 = ((int64_t) amod_1 * (int64_t) lfo_val) >> 24; | |||
uint32_t amod_2 = ((int64_t) ctrls->amp_mod * (int64_t) lfo_val) >> 7; // Q?? :| | |||
uint32_t amd_mod = max(amod_1, amod_2); | |||
// ==== EG AMP MOD ==== | |||
uint32_t amod_3 = (ctrls->eg_mod+1) << 17; | |||
amd_mod = max((1<<24) - amod_3, amd_mod); | |||
// ==== OP RENDER ==== | |||
for (int op = 0; op < 6; op++) { | |||
//int32_t gain = pow(2, 10 + level * (1.0 / (1 << 24))); | |||
params_[op].freq = Freqlut::lookup(basepitch_[op] + pitchmod); | |||
int32_t level = env_[op].getsample(); | |||
if (ampmodsens_[op] != 0) { | |||
uint32_t sensamp = ((uint64_t) amd) * ((uint64_t) ampmodsens_[op]) >> 24; | |||
if ( ctrls->opSwitch[op] == '0' ) { | |||
env_[op].getsample(); // advance the envelop even if it is not playing | |||
params_[op].level_in = 0; | |||
} else { | |||
//int32_t gain = pow(2, 10 + level * (1.0 / (1 << 24))); | |||
params_[op].freq = Freqlut::lookup(basepitch_[op] + pitch_mod); | |||
// TODO: mehhh.. this needs some real tuning. | |||
uint32_t pt = exp(((float)sensamp)/262144 * 0.07 + 12.2); | |||
uint32_t ldiff = ((uint64_t)level) * (((uint64_t)pt<<4)) >> 28; | |||
level -= ldiff; | |||
int32_t level = env_[op].getsample(); | |||
if (ampmodsens_[op] != 0) { | |||
uint32_t sensamp = ((uint64_t) amd_mod) * ((uint64_t) ampmodsens_[op]) >> 24; | |||
// TODO: mehhh.. this needs some real tuning. | |||
uint32_t pt = exp(((float)sensamp)/262144 * 0.07 + 12.2); | |||
uint32_t ldiff = ((uint64_t)level) * (((uint64_t)pt<<4)) >> 28; | |||
level -= ldiff; | |||
} | |||
params_[op].level_in = level; | |||
} | |||
params_[op].level_in = level; | |||
} | |||
ctrls->core->render(buf, params_, algorithm_, fb_buf_, fb_shift_, ctrls); | |||
ctrls->core->render(buf, params_, algorithm_, fb_buf_, fb_shift_); | |||
} | |||
void Dx7Note::keyup() { | |||
for (int op = 0; op < 6; op++) { | |||
env_[op].keydown(false); | |||
for (int op = 0; op < 6; op++) { | |||
env_[op].keydown(false); | |||
} | |||
pitchenv_.keydown(false); | |||
} | |||
} | |||
void Dx7Note::update(const char patch[156], int midinote) { | |||
for (int op = 0; op < 6; op++) { | |||
int off = op * 21; | |||
int mode = patch[off + 17]; | |||
int coarse = patch[off + 18]; | |||
int fine = patch[off + 19]; | |||
int detune = patch[off + 20]; | |||
basepitch_[op] = osc_freq(midinote, mode, coarse, fine, detune); | |||
ampmodsens_[op] = ampmodsenstab[patch[off + 14] & 3]; | |||
} | |||
algorithm_ = patch[134]; | |||
int feedback = patch[135]; | |||
fb_shift_ = feedback != 0 ? 8 - feedback : 16; | |||
pitchmoddepth_ = (patch[139] * 165) >> 6; | |||
pitchmodsens_ = pitchmodsenstab[patch[143] & 7]; | |||
ampmoddepth_ = (patch[140] * 165) >> 6; | |||
void Dx7Note::update(const uint8_t patch[156], int midinote, int velocity) { | |||
int rates[4]; | |||
int levels[4]; | |||
for (int op = 0; op < 6; op++) { | |||
int off = op * 21; | |||
int mode = patch[off + 17]; | |||
int coarse = patch[off + 18]; | |||
int fine = patch[off + 19]; | |||
int detune = patch[off + 20]; | |||
basepitch_[op] = osc_freq(midinote, mode, coarse, fine, detune); | |||
ampmodsens_[op] = ampmodsenstab[patch[off + 14] & 3]; | |||
for (int i = 0; i < 4; i++) { | |||
rates[i] = patch[off + i]; | |||
levels[i] = patch[off + 4 + i]; | |||
} | |||
int outlevel = patch[off + 16]; | |||
outlevel = Env::scaleoutlevel(outlevel); | |||
int level_scaling = ScaleLevel(midinote, patch[off + 8], patch[off + 9], | |||
patch[off + 10], patch[off + 11], patch[off + 12]); | |||
outlevel += level_scaling; | |||
outlevel = min(127, outlevel); | |||
outlevel = outlevel << 5; | |||
outlevel += ScaleVelocity(velocity, patch[off + 15]); | |||
outlevel = max(0, outlevel); | |||
int rate_scaling = ScaleRate(midinote, patch[off + 13]); | |||
env_[op].update(rates, levels, outlevel, rate_scaling); | |||
} | |||
algorithm_ = patch[134]; | |||
int feedback = patch[135]; | |||
fb_shift_ = feedback != 0 ? FEEDBACK_BITDEPTH - feedback : 16; | |||
pitchmoddepth_ = (patch[139] * 165) >> 6; | |||
pitchmodsens_ = pitchmodsenstab[patch[143] & 7]; | |||
ampmoddepth_ = (patch[140] * 165) >> 6; | |||
} | |||
void Dx7Note::peekVoiceStatus(VoiceStatus &status) { | |||
for(int i=0;i<6;i++) { | |||
status.amp[i] = Exp2::lookup(params_[i].level_in - (14 * (1 << 24))); | |||
env_[i].getPosition(&status.ampStep[i]); | |||
} | |||
pitchenv_.getPosition(&status.pitchStep); | |||
for(int i=0;i<6;i++) { | |||
status.amp[i] = Exp2::lookup(params_[i].level_in - (14 * (1 << 24))); | |||
env_[i].getPosition(&status.ampStep[i]); | |||
} | |||
pitchenv_.getPosition(&status.pitchStep); | |||
} | |||
/** | |||
@@ -285,7 +306,6 @@ void Dx7Note::transferSignal(Dx7Note &src) { | |||
for (int i=0;i<6;i++) { | |||
params_[i].gain_out = src.params_[i].gain_out; | |||
params_[i].phase = src.params_[i].phase; | |||
//params_[i].phase = 0; | |||
} | |||
} | |||
@@ -1,12 +1,13 @@ | |||
/* | |||
* Copyright 2016-2017 Pascal Gauthier. | |||
* Copyright 2012 Google Inc. | |||
* | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
@@ -28,48 +29,48 @@ | |||
#include "fm_core.h" | |||
struct VoiceStatus { | |||
uint32_t amp[6]; | |||
char ampStep[6]; | |||
char pitchStep; | |||
uint32_t amp[6]; | |||
char ampStep[6]; | |||
char pitchStep; | |||
}; | |||
class Dx7Note { | |||
public: | |||
Dx7Note(); | |||
void init(const char patch[156], int midinote, int velocity); | |||
// Note: this _adds_ to the buffer. Interesting question whether it's | |||
// worth it... | |||
void compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, | |||
const Controllers *ctrls); | |||
void keyup(); | |||
public: | |||
Dx7Note(); | |||
void init(const uint8_t patch[156], int midinote, int velocity); | |||
// TODO: some way of indicating end-of-note. Maybe should be a return | |||
// value from the compute method? (Having a count return from keyup | |||
// is also tempting, but if there's a dynamic parameter change after | |||
// keyup, that won't work. | |||
// PG:add the update | |||
void update(const char patch[156], int midinote); | |||
void peekVoiceStatus(VoiceStatus &status); | |||
void transferState(Dx7Note& src); | |||
void transferSignal(Dx7Note &src); | |||
void oscSync(); | |||
// Note: this _adds_ to the buffer. Interesting question whether it's | |||
// worth it... | |||
void compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, | |||
const Controllers *ctrls); | |||
void keyup(); | |||
// TODO: some way of indicating end-of-note. Maybe should be a return | |||
// value from the compute method? (Having a count return from keyup | |||
// is also tempting, but if there's a dynamic parameter change after | |||
// keyup, that won't work. | |||
// PG:add the update | |||
void update(const uint8_t patch[156], int midinote, int velocity); | |||
void peekVoiceStatus(VoiceStatus &status); | |||
void transferState(Dx7Note& src); | |||
void transferSignal(Dx7Note &src); | |||
void oscSync(); | |||
private: | |||
Env env_[6]; | |||
FmOpParams params_[6]; | |||
PitchEnv pitchenv_; | |||
int32_t basepitch_[6]; | |||
int32_t fb_buf_[2]; | |||
int32_t fb_shift_; | |||
int32_t ampmodsens_[6]; | |||
private: | |||
Env env_[6]; | |||
FmOpParams params_[6]; | |||
PitchEnv pitchenv_; | |||
int32_t basepitch_[6]; | |||
int32_t fb_buf_[2]; | |||
int32_t fb_shift_; | |||
int32_t ampmodsens_[6]; | |||
int ampmoddepth_; | |||
int algorithm_; | |||
int pitchmoddepth_; | |||
int pitchmodsens_; | |||
int ampmoddepth_; | |||
int algorithm_; | |||
int pitchmoddepth_; | |||
int pitchmodsens_; | |||
}; | |||
#endif // SYNTH_DX7NOTE_H_ |
@@ -1,12 +1,13 @@ | |||
/* | |||
* Copyright 2017 Pascal Gauthier. | |||
* Copyright 2012 Google Inc. | |||
* | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
@@ -24,91 +25,100 @@ | |||
uint32_t Env::sr_multiplier = (1<<24); | |||
const int levellut[] = { | |||
0, 5, 9, 13, 17, 20, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 42, 43, 45, 46 | |||
}; | |||
void Env::init_sr(double sampleRate) { | |||
sr_multiplier = (44100.0 / sampleRate) * (1<<24); | |||
} | |||
void Env::init(const int r[4], const int l[4], int32_t ol, int rate_scaling) { | |||
for (int i = 0; i < 4; i++) { | |||
rates_[i] = r[i]; | |||
levels_[i] = l[i]; | |||
} | |||
outlevel_ = ol; | |||
rate_scaling_ = rate_scaling; | |||
level_ = 0; | |||
down_ = true; | |||
advance(0); | |||
for (int i = 0; i < 4; i++) { | |||
rates_[i] = r[i]; | |||
levels_[i] = l[i]; | |||
} | |||
outlevel_ = ol; | |||
rate_scaling_ = rate_scaling; | |||
level_ = 0; | |||
down_ = true; | |||
advance(0); | |||
} | |||
int32_t Env::getsample() { | |||
if (ix_ < 3 || ((ix_ < 4) && !down_)) { | |||
if (rising_) { | |||
const int jumptarget = 1716; | |||
if (level_ < (jumptarget << 16)) { | |||
level_ = jumptarget << 16; | |||
} | |||
level_ += (((17 << 24) - level_) >> 24) * inc_; | |||
// TODO: should probably be more accurate when inc is large | |||
if (level_ >= targetlevel_) { | |||
level_ = targetlevel_; | |||
advance(ix_ + 1); | |||
} | |||
} else { // !rising | |||
level_ -= inc_; | |||
if (level_ <= targetlevel_) { | |||
level_ = targetlevel_; | |||
advance(ix_ + 1); | |||
} | |||
if (ix_ < 3 || ((ix_ < 4) && !down_)) { | |||
if (rising_) { | |||
const int jumptarget = 1716; | |||
if (level_ < (jumptarget << 16)) { | |||
level_ = jumptarget << 16; | |||
} | |||
level_ += (((17 << 24) - level_) >> 24) * inc_; | |||
// TODO: should probably be more accurate when inc is large | |||
if (level_ >= targetlevel_) { | |||
level_ = targetlevel_; | |||
advance(ix_ + 1); | |||
} | |||
} else { // !rising | |||
level_ -= inc_; | |||
if (level_ <= targetlevel_) { | |||
level_ = targetlevel_; | |||
advance(ix_ + 1); | |||
} | |||
} | |||
} | |||
} | |||
// TODO: this would be a good place to set level to 0 when under threshold | |||
return level_; | |||
// TODO: this would be a good place to set level to 0 when under threshold | |||
return level_; | |||
} | |||
void Env::keydown(bool d) { | |||
if (down_ != d) { | |||
down_ = d; | |||
advance(d ? 0 : 3); | |||
} | |||
} | |||
void Env::setparam(int param, int value) { | |||
if (param < 4) { | |||
rates_[param] = value; | |||
} else if (param < 8) { | |||
levels_[param - 4] = value; | |||
} | |||
// Unknown parameter, ignore for now | |||
if (down_ != d) { | |||
down_ = d; | |||
advance(d ? 0 : 3); | |||
} | |||
} | |||
const int levellut[] = { | |||
0, 5, 9, 13, 17, 20, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 42, 43, 45, 46 | |||
}; | |||
int Env::scaleoutlevel(int outlevel) { | |||
return outlevel >= 20 ? 28 + outlevel : levellut[outlevel]; | |||
return outlevel >= 20 ? 28 + outlevel : levellut[outlevel]; | |||
} | |||
void Env::advance(int newix) { | |||
ix_ = newix; | |||
if (ix_ < 4) { | |||
int newlevel = levels_[ix_]; | |||
int actuallevel = scaleoutlevel(newlevel) >> 1; | |||
actuallevel = (actuallevel << 6) + outlevel_ - 4256; | |||
actuallevel = actuallevel < 16 ? 16 : actuallevel; | |||
// level here is same as Java impl | |||
targetlevel_ = actuallevel << 16; | |||
rising_ = (targetlevel_ > level_); | |||
// rate | |||
int qrate = (rates_[ix_] * 41) >> 6; | |||
qrate += rate_scaling_; | |||
qrate = min(qrate, 63); | |||
inc_ = (4 + (qrate & 3)) << (2 + LG_N + (qrate >> 2)); | |||
ix_ = newix; | |||
if (ix_ < 4) { | |||
int newlevel = levels_[ix_]; | |||
int actuallevel = scaleoutlevel(newlevel) >> 1; | |||
actuallevel = (actuallevel << 6) + outlevel_ - 4256; | |||
actuallevel = actuallevel < 16 ? 16 : actuallevel; | |||
// level here is same as Java impl | |||
targetlevel_ = actuallevel << 16; | |||
rising_ = (targetlevel_ > level_); | |||
// rate | |||
int qrate = (rates_[ix_] * 41) >> 6; | |||
qrate += rate_scaling_; | |||
qrate = min(qrate, 63); | |||
inc_ = (4 + (qrate & 3)) << (2 + LG_N + (qrate >> 2)); | |||
// meh, this should be fixed elsewhere | |||
inc_ = ((int64_t)inc_ * (int64_t)sr_multiplier) >> 24; | |||
} | |||
} | |||
// meh, this should be fixed elsewhere | |||
inc_ = ((int64_t)inc_ * (int64_t)sr_multiplier) >> 24; | |||
} | |||
void Env::update(const int r[4], const int l[4], int32_t ol, int rate_scaling) { | |||
for (int i = 0; i < 4; i++) { | |||
rates_[i] = r[i]; | |||
levels_[i] = l[i]; | |||
} | |||
outlevel_ = ol; | |||
rate_scaling_ = rate_scaling; | |||
if ( down_ ) { | |||
// for now we simply reset ourselve at level 3 | |||
int newlevel = levels_[2]; | |||
int actuallevel = scaleoutlevel(newlevel) >> 1; | |||
actuallevel = (actuallevel << 6) - 4256; | |||
actuallevel = actuallevel < 16 ? 16 : actuallevel; | |||
targetlevel_ = actuallevel << 16; | |||
advance(2); | |||
} | |||
} | |||
void Env::getPosition(char *step) { | |||
@@ -1,4 +1,5 @@ | |||
/* | |||
* Copyright 2017 Pascal Gauthier. | |||
* Copyright 2012 Google Inc. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
@@ -31,6 +32,8 @@ class Env { | |||
void init(const int rates[4], const int levels[4], int outlevel, | |||
int rate_scaling); | |||
void update(const int rates[4], const int levels[4], int outlevel, | |||
int rate_scaling); | |||
// Result is in Q24/doubling log format. Also, result is subsampled | |||
// for every N samples. | |||
// A couple more things need to happen for this to be used as a gain | |||
@@ -40,7 +43,6 @@ class Env { | |||
int32_t getsample(); | |||
void keydown(bool down); | |||
void setparam(int param, int value); | |||
static int scaleoutlevel(int outlevel); | |||
void getPosition(char *step); | |||
@@ -30,9 +30,9 @@ const FmAlgorithm FmCore::algorithms[32] = { | |||
{ { 0xc1, 0x11, 0x11, 0x14, 0x01, 0x14 } }, // 1 | |||
{ { 0x01, 0x11, 0x11, 0x14, 0xc1, 0x14 } }, // 2 | |||
{ { 0xc1, 0x11, 0x14, 0x01, 0x11, 0x14 } }, // 3 | |||
{ { 0xc1, 0x11, 0x94, 0x01, 0x11, 0x14 } }, // 4 ** EXCEPTION VIA CODE | |||
{ { 0xc1, 0x11, 0x94, 0x01, 0x11, 0x14 } }, // 4 | |||
{ { 0xc1, 0x14, 0x01, 0x14, 0x01, 0x14 } }, // 5 | |||
{ { 0xc1, 0x94, 0x01, 0x14, 0x01, 0x14 } }, // 6 ** EXCEPTION VIA CODE | |||
{ { 0xc1, 0x94, 0x01, 0x14, 0x01, 0x14 } }, // 6 | |||
{ { 0xc1, 0x11, 0x05, 0x14, 0x01, 0x14 } }, // 7 | |||
{ { 0x01, 0x11, 0xc5, 0x14, 0x01, 0x14 } }, // 8 | |||
{ { 0x01, 0x11, 0x05, 0x14, 0xc1, 0x14 } }, // 9 | |||
@@ -90,8 +90,7 @@ void FmCore::dump() { | |||
#endif | |||
} | |||
void FmCore::render(int32_t *output, FmOpParams *params, int algorithm, | |||
int32_t *fb_buf, int feedback_shift, const Controllers *controller) { | |||
void FmCore::render(int32_t *output, FmOpParams *params, int algorithm, int32_t *fb_buf, int feedback_shift) { | |||
const int kLevelThresh = 1120; | |||
const FmAlgorithm alg = algorithms[algorithm]; | |||
bool has_contents[3] = { true, false, false }; | |||
@@ -1,12 +1,12 @@ | |||
/* | |||
* Copyright 2012 Google Inc. | |||
* | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
@@ -45,14 +45,13 @@ public: | |||
}; | |||
class FmCore { | |||
public: | |||
virtual ~FmCore() {}; | |||
static void dump(); | |||
virtual void render(int32_t *output, FmOpParams *params, int algorithm, | |||
int32_t *fb_buf, int32_t feedback_gain, const Controllers *controller); | |||
protected: | |||
AlignedBuf<int32_t, N>buf_[2]; | |||
const static FmAlgorithm algorithms[32]; | |||
public: | |||
virtual ~FmCore() {}; | |||
static void dump(); | |||
virtual void render(int32_t *output, FmOpParams *params, int algorithm, int32_t *fb_buf, int32_t feedback_gain); | |||
protected: | |||
AlignedBuf<int32_t, N>buf_[2]; | |||
const static FmAlgorithm algorithms[32]; | |||
}; | |||
#endif // __FM_CORE_H |
@@ -1,12 +1,12 @@ | |||
/* | |||
* Copyright 2013 Google Inc. | |||
* | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
@@ -24,74 +24,74 @@ | |||
uint32_t Lfo::unit_; | |||
void Lfo::init(double sample_rate) { | |||
// constant is 1 << 32 / 15.5s / 11 | |||
Lfo::unit_ = (int32_t)(N * 25190424 / sample_rate + 0.5); | |||
// constant is 1 << 32 / 15.5s / 11 | |||
Lfo::unit_ = (int32_t)(N * 25190424 / sample_rate + 0.5); | |||
} | |||
void Lfo::reset(const char params[6]) { | |||
int rate = params[0]; // 0..99 | |||
int sr = rate == 0 ? 1 : (165 * rate) >> 6; | |||
sr *= sr < 160 ? 11 : (11 + ((sr - 160) >> 4)); | |||
delta_ = unit_ * sr; | |||
int a = 99 - params[1]; // LFO delay | |||
if (a == 99) { | |||
delayinc_ = ~0u; | |||
delayinc2_ = ~0u; | |||
} else { | |||
a = (16 + (a & 15)) << (1 + (a >> 4)); | |||
delayinc_ = unit_ * a; | |||
a &= 0xff80; | |||
a = max(0x80, a); | |||
delayinc2_ = unit_ * a; | |||
} | |||
waveform_ = params[5]; | |||
sync_ = params[4] != 0; | |||
void Lfo::reset(const uint8_t params[6]) { | |||
int rate = params[0]; // 0..99 | |||
int sr = rate == 0 ? 1 : (165 * rate) >> 6; | |||
sr *= sr < 160 ? 11 : (11 + ((sr - 160) >> 4)); | |||
delta_ = unit_ * sr; | |||
int a = 99 - params[1]; // LFO delay | |||
if (a == 99) { | |||
delayinc_ = ~0u; | |||
delayinc2_ = ~0u; | |||
} else { | |||
a = (16 + (a & 15)) << (1 + (a >> 4)); | |||
delayinc_ = unit_ * a; | |||
a &= 0xff80; | |||
a = max(0x80, a); | |||
delayinc2_ = unit_ * a; | |||
} | |||
waveform_ = params[5]; | |||
sync_ = params[4] != 0; | |||
} | |||
int32_t Lfo::getsample() { | |||
phase_ += delta_; | |||
int32_t x; | |||
switch (waveform_) { | |||
case 0: // triangle | |||
x = phase_ >> 7; | |||
x ^= -(phase_ >> 31); | |||
x &= (1 << 24) - 1; | |||
return x; | |||
case 1: // sawtooth down | |||
return (~phase_ ^ (1U << 31)) >> 8; | |||
case 2: // sawtooth up | |||
return (phase_ ^ (1U << 31)) >> 8; | |||
case 3: // square | |||
return ((~phase_) >> 7) & (1 << 24); | |||
case 4: // sine | |||
return (1 << 23) + (Sin::lookup(phase_ >> 8) >> 1); | |||
case 5: // s&h | |||
if (phase_ < delta_) { | |||
randstate_ = (randstate_ * 179 + 17) & 0xff; | |||
phase_ += delta_; | |||
int32_t x; | |||
switch (waveform_) { | |||
case 0: // triangle | |||
x = phase_ >> 7; | |||
x ^= -(phase_ >> 31); | |||
x &= (1 << 24) - 1; | |||
return x; | |||
case 1: // sawtooth down | |||
return (~phase_ ^ (1U << 31)) >> 8; | |||
case 2: // sawtooth up | |||
return (phase_ ^ (1U << 31)) >> 8; | |||
case 3: // square | |||
return ((~phase_) >> 7) & (1 << 24); | |||
case 4: // sine | |||
return (1 << 23) + (Sin::lookup(phase_ >> 8) >> 1); | |||
case 5: // s&h | |||
if (phase_ < delta_) { | |||
randstate_ = (randstate_ * 179 + 17) & 0xff; | |||
} | |||
x = randstate_ ^ 0x80; | |||
return (x + 1) << 16; | |||
} | |||
x = randstate_ ^ 0x80; | |||
return (x + 1) << 16; | |||
} | |||
return 1 << 23; | |||
return 1 << 23; | |||
} | |||
int32_t Lfo::getdelay() { | |||
uint32_t delta = delaystate_ < (1U << 31) ? delayinc_ : delayinc2_; | |||
uint32_t d = delaystate_ + delta; | |||
if (d < delayinc_) { | |||
return 1 << 24; | |||
} | |||
delaystate_ = d; | |||
if (d < (1U << 31)) { | |||
return 0; | |||
} else { | |||
return (d >> 7) & ((1 << 24) - 1); | |||
} | |||
uint32_t delta = delaystate_ < (1U << 31) ? delayinc_ : delayinc2_; | |||
uint64_t d = ((uint64_t)delaystate_) + delta; | |||
if (d > ~0u) { | |||
return 1 << 24; | |||
} | |||
delaystate_ = d; | |||
if (d < (1U << 31)) { | |||
return 0; | |||
} else { | |||
return (d >> 7) & ((1 << 24) - 1); | |||
} | |||
} | |||
void Lfo::keydown() { | |||
if (sync_) { | |||
phase_ = (1U << 31) - 1; | |||
} | |||
delaystate_ = 0; | |||
if (sync_) { | |||
phase_ = (1U << 31) - 1; | |||
} | |||
delaystate_ = 0; | |||
} |
@@ -19,7 +19,7 @@ | |||
class Lfo { | |||
public: | |||
static void init(double sample_rate); | |||
void reset(const char params[6]); | |||
void reset(const uint8_t params[6]); | |||
// result is 0..1 in Q24 | |||
int32_t getsample(); | |||
@@ -38,6 +38,7 @@ typedef __int16 SInt16; | |||
#endif | |||
#endif | |||
#include "../Dexed.h" | |||
// #undef SynthMemoryBarrier() | |||
@@ -57,11 +58,4 @@ inline static T max(const T& a, const T& b) { | |||
return a > b ? a : b; | |||
} | |||
void dexed_trace(const char *source, const char *fmt, ...); | |||
#define QER(n,b) ( ((float)n)/(1<<b) ) | |||
#define TRACE(fmt, ...) | |||
#endif // __SYNTH_H |