Skip to content

Commit

Permalink
Cherry pick setupHelp()
Browse files Browse the repository at this point in the history
  • Loading branch information
christofmuc committed Jan 15, 2021
1 parent f4772dc commit 4aa6c5d
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 29 deletions.
70 changes: 65 additions & 5 deletions The-Orm/AdaptationView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,58 @@
#include "ProgramDumpCapability.h"
#include "BankDumpCapability.h"

#include "BundledAdaptation.h"

#include <boost/format.hpp>

namespace knobkraft {

AdaptationView::AdaptationView()
AdaptationView::AdaptationView() : extraFunctions_(900, LambdaButtonStrip::Direction::Horizontal)
{
addAndMakeVisible(adaptationInfo_);

LambdaButtonStrip::TButtonMap buttons = {
{ "ReloadAdaptation", { 0, "Reload python file", [this]() {
if (adaptation_ && adaptation_->isFromFile()) {
adaptation_->reloadPython();
setupForAdaptation(adaptation_);
}
else {
AlertWindow::showMessageBox(AlertWindow::InfoIcon, "Not a user defined adaptation", "Only Adaptation modules that are loaded from a Python script can be reloaded");
}
}}},
{ "EditAdaptation", { 1, "Edit python file", [this]() {
if (adaptation_) {
if (!adaptation_->isFromFile()) {
// Not possible yet, but offer to break out from binary
if (AlertWindow::showOkCancelBox(AlertWindow::InfoIcon, "Not a user defined adaptation", "This adaptation is a built-in module. We can break it out into the file system so you can edit it\n\n"
"Should you wish to go back to the built-in version, just delete the file that will be created!", "Break out", "Cancel")) {
if (BundledAdaptations::breakOut(adaptation_->getName())) {
AlertWindow::showMessageBox(AlertWindow::InfoIcon, "File created", "We created the file. Please restart the KnobKraft Orm and continue with the edit operation");
}
}
return;
}

// Just reveal to user - launching python files with the URL command is useless, because it will probably just try to run the python script instead of opening an editor.
File adaptationSource(adaptation_->getSourceFilePath());
if (adaptationSource.exists()) {
adaptationSource.revealToUser();
}
}
}}}
};

extraFunctions_.setButtonDefinitions(buttons);
addAndMakeVisible(extraFunctions_);
addAndMakeVisible(setupHelp_);
addAndMakeVisible(knobkraftWiki_);
}

void AdaptationView::setupForAdaptation(std::shared_ptr<GenericAdaptation> adaptationSynth)
{
adaptation_ = adaptationSynth;
std::string infoText;

infoText = (boost::format("Implementation information for the adaptation for the '%s':\n\n") % adaptationSynth->getName()).str();

bool failure = false;
Expand Down Expand Up @@ -61,14 +100,35 @@ namespace knobkraft {
}

adaptationInfo_.setText(infoText, false);

// Setup help

setupHelp_.setText(adaptation_->setupHelpText(), false);
knobkraftWiki_.setButtonText(adaptation_->getName() + " in the KnobKraft Wiki");
String pageName = String(adaptation_->getName()).replace(" ", "-");
knobkraftWiki_.setURL(URL("https://github.com/christofmuc/KnobKraft-orm/wiki/" + pageName));
}

void AdaptationView::resized()
{
auto area = getLocalBounds();
int width = std::min(area.getWidth(), 600);
int height = std::min(area.getHeight(), 800);
adaptationInfo_.setBounds(area.removeFromTop(height).withTrimmedBottom(8).withSizeKeepingCentre(width, height).reduced(8));

extraFunctions_.setBounds(area.removeFromBottom(60).reduced(8));

// Left column
FlexBox leftColumn;
leftColumn.flexDirection = FlexBox::Direction::column;
leftColumn.items.add(FlexItem(knobkraftWiki_).withHeight(30));
leftColumn.items.add(FlexItem(setupHelp_).withFlex(1.0f).withMargin({ 8, 0, 0, 0 }));;

// Two column view
FlexBox layout;
layout.flexDirection = FlexBox::Direction::row;
layout.alignContent = FlexBox::AlignContent::stretch;
layout.justifyContent = FlexBox::JustifyContent::center;
layout.items.add(FlexItem(leftColumn).withWidth(600).withMargin({ 0, 4, 0, 0 }));
layout.items.add(FlexItem(adaptationInfo_).withWidth(600).withMargin({ 0, 0, 0, 4 }));
layout.performLayout(area.reduced(8));
}

}
6 changes: 6 additions & 0 deletions The-Orm/AdaptationView.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include "GenericAdaptation.h"
#include "InfoText.h"

#include "LambdaButtonStrip.h"

namespace knobkraft {

class AdaptationView : public Component {
Expand All @@ -24,7 +26,11 @@ namespace knobkraft {
private:
std::shared_ptr<GenericAdaptation> adaptation_;

InfoText setupHelp_;
InfoText adaptationInfo_;
HyperlinkButton knobkraftWiki_;

LambdaButtonStrip extraFunctions_;
};

}
Expand Down
1 change: 1 addition & 0 deletions The-Orm/PatchPerSynthList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ void PatchPerSynthList::changeListenerCallback(ChangeBroadcaster* source)
}
}
}

14 changes: 14 additions & 0 deletions adaptions/Adaptation Programming Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,20 @@ For example, the good old Korg DW8000 has 64 slots, but has banks 1 to 8 and in
def friendlyProgramName(program):
return "%d%d" % (program // 8 + 1, (program % 8) + 1)

## Leaving helpful setup information specific for a synth

Especially some of our more vintage synths require some preset done, sometimes after every power on, before they can be accessed by the KnobKraft Orm. You can implement the following optional function to return a text displayed to the user in the synth's settings tab:

def setupHelp():

This just returns a text string displayed. Make sure to quote a possible multiline text correctly. The return character has to be encoded as `\n`, and as it is Python you need a trailing backslash if the line continues into the next line. Or just copy this example and change the text:

def setupHelp():
return "The DSI Prophet 12 has two relevant global settings:\n\n" \
"1. You must set MIDI Sysex Enable to On\n" \
"2. You must choose the MIDI Sysex Cable.\n\n" \
"Options are DIN MIDI cable or the USB for sysex. USB is much faster.\n\n" \
"Both settings are accessible via the GLOBALS menu."

## Examples

Expand Down
36 changes: 35 additions & 1 deletion adaptions/BundledAdaptation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
#include "BundledAdaptation.h"

#include "CompiledAdaptations.h"
#include "GenericAdaptation.h"

namespace knobkraft {

std::vector<knobkraft::BundledAdaptation> gBundledAdaptations()
std::vector<knobkraft::BundledAdaptation> BundledAdaptations::getAll()
{
return {
{ "Alesis Andromeda A6", "Alesis_Andromeda_A6", std::string(AlesisAndromedaA6_py, AlesisAndromedaA6_py + AlesisAndromedaA6_py_size) },
Expand Down Expand Up @@ -42,4 +43,37 @@ namespace knobkraft {
};
}

bool BundledAdaptations::breakOut(std::string synthName)
{
// Find it
BundledAdaptation adaptation;
for (auto a : getAll()) {
if (a.synthName == synthName) {
adaptation = a;
break;
}
}
if (adaptation.pythonModuleName.empty()) {
SimpleLogger::instance()->postMessage("Program error - could not find adaptation for synth " + synthName);
return false;
}

auto dir = GenericAdaptation::getAdaptationDirectory();

// Copy out source code
File target = dir.getChildFile(adaptation.pythonModuleName + ".py");
if (target.exists()) {
juce::AlertWindow::showMessageBox(AlertWindow::AlertIconType::WarningIcon, "File exists", "There is already a file for this adaptation, which we will not overwrite.");
return false;
}

FileOutputStream out(target);
#if WIN32
out.writeText(adaptation.adaptationSourceCode, false, false, "\r\n");
#else
out.writeText(adaptation.adaptationSourceCode, false, false, "\n");
#endif
return true;
}

}
5 changes: 4 additions & 1 deletion adaptions/BundledAdaptation.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ namespace knobkraft {
std::string adaptationSourceCode;
};

extern std::vector<BundledAdaptation> gBundledAdaptations();
struct BundledAdaptations {
static std::vector<BundledAdaptation> getAll();
static bool breakOut(std::string synthName);
};

}

22 changes: 3 additions & 19 deletions adaptions/CreateNewAdaptationDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ namespace knobkraft {
cancel_.addListener(this);

StringArray templateList;
for (auto const &a : gBundledAdaptations()) {
for (auto const &a : BundledAdaptations::getAll()) {
templateList.add(a.synthName);
}

Expand Down Expand Up @@ -68,24 +68,8 @@ namespace knobkraft {
return false;
}

auto adaptation = gBundledAdaptations()[selected];

auto dir = GenericAdaptation::getAdaptationDirectory();

// Copy out source code
File target = dir.getChildFile(adaptation.pythonModuleName + ".py");
if (target.exists()) {
juce::AlertWindow::showMessageBox(AlertWindow::AlertIconType::WarningIcon, "File exists", "There is already a file for this adaptation, which we will not overwrite.");
return false;
}

FileOutputStream out(target);
#if WIN32
out.writeText(adaptation.adaptationSourceCode, false, false, "\r\n");
#else
out.writeText(adaptation.adaptationSourceCode, false, false, "\n");
#endif
return true;
auto adaptation = BundledAdaptations::getAll()[selected];
return BundledAdaptations::breakOut(adaptation.synthName);
}

void CreateNewAdaptationDialog::buttonClicked(Button* button)
Expand Down
11 changes: 10 additions & 1 deletion adaptions/DSI Pro 2.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

device_ID = 0b00101100 # See Page 134 of the Pro 2 manual


def name():
return "DSI Pro 2"

Expand Down Expand Up @@ -86,7 +87,7 @@ def nameFromDump(message):
dataBlock = message[4:-1]
if len(dataBlock) > 0:
patchData = unescapeSysex(dataBlock)
return ''.join([chr(x) for x in patchData[378:378+20]])
return ''.join([chr(x) for x in patchData[378:378 + 20]])
return "Invalid"


Expand All @@ -108,6 +109,14 @@ def convertToProgramDump(channel, message, patchNo):
raise Exception("Neither edit buffer nor program dump - can't be converted")


def setupHelp():
return "The DSI Pro 2 has two relevant global settings:\n\n" \
"1. You must set MIDI Sysex Enable to On\n" \
"2. You must choose the MIDI Sysex Cable.\n\n" \
"Options are DIN MIDI cable or the USB for sysex. USB is much faster.\n\n" \
"Both settings are accessible via the GLOBALS menu."


def unescapeSysex(sysex):
result = []
dataIndex = 0
Expand Down
8 changes: 8 additions & 0 deletions adaptions/DSI Prophet 12.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ def renamePatch(message, new_name):
return message[:header_len] + escapeSysex(data) + [0xf7]


def setupHelp():
return "The DSI Prophet 12 has two relevant global settings:\n\n" \
"1. You must set MIDI Sysex Enable to On\n" \
"2. You must choose the MIDI Sysex Cable.\n\n" \
"Options are DIN MIDI cable or the USB for sysex. USB is much faster.\n\n" \
"Both settings are accessible via the GLOBALS menu."


def getDataBlock(message):
if isSingleProgramDump(message):
return message[6:-1]
Expand Down
51 changes: 49 additions & 2 deletions adaptions/GenericAdaptation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ namespace knobkraft {
*kGeneralMessageDelay = "generalMessageDelay",
*kCalculateFingerprint = "calculateFingerprint",
*kFriendlyBankName = "friendlyBankName",
*kFriendlyProgramName= "friendlyProgramName";
*kFriendlyProgramName = "friendlyProgramName",
*kSetupHelp = "setupHelp";

std::vector<const char *> kAdapatationPythonFunctionNames = {
kName,
Expand Down Expand Up @@ -87,6 +88,7 @@ namespace knobkraft {
kCalculateFingerprint,
kFriendlyBankName,
kFriendlyProgramName,
kSetupHelp,
};

std::vector<const char *> kMinimalRequiredFunctionNames = {
Expand Down Expand Up @@ -306,7 +308,7 @@ namespace knobkraft {
}

// Then, iterate over the list of built-in adaptations and add those which are not present in the directory
auto adaptations = gBundledAdaptations();
auto adaptations = BundledAdaptations::getAll();
for (auto const &b : adaptations) {
createCompiledAdaptationModule(b.pythonModuleName, b.adaptationSourceCode, result);
}
Expand All @@ -321,6 +323,31 @@ namespace knobkraft {
return py::hasattr(*adaptation_module, functionName.c_str());
}

bool GenericAdaptation::isFromFile() const
{
return !filepath_.empty();
}

std::string GenericAdaptation::getSourceFilePath() const
{
return adaptation_module.attr("__file__").cast<std::string>();
}

void GenericAdaptation::reloadPython()
{
try {
adaptation_module.reload();
logNamespace();
}
catch (py::error_already_set &ex) {
logAdaptationError(kNumberOfBanks, ex);
ex.restore();
}
catch (std::exception &ex) {
logAdaptationError(kNumberOfBanks, ex);
}
}

int GenericAdaptation::numberOfBanks() const
{
try {
Expand Down Expand Up @@ -430,6 +457,26 @@ namespace knobkraft {
return Synth::friendlyProgramName(programNo);
}

std::string GenericAdaptation::setupHelpText() const
{
if (!pythonModuleHasFunction("setupHelp")) {
return Synth::setupHelpText();
}

try {
return py::cast<std::string>(callMethod(kSetupHelp));
}
catch (py::error_already_set &ex) {
logAdaptationError(kSetupHelp, ex);
ex.restore();
return Synth::setupHelpText();
}
catch (std::exception &ex) {
logAdaptationError(kSetupHelp, ex);
return Synth::setupHelpText();
}
}

std::vector<juce::MidiMessage> GenericAdaptation::deviceDetect(int channel)
{
try {
Expand Down
4 changes: 4 additions & 0 deletions adaptions/GenericAdaptation.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,13 @@ namespace knobkraft {
// This generic synth method is overridden to allow throttling of messages for older synths like the Korg MS2000
virtual void sendBlockOfMessagesToSynth(std::string const& midiOutput, MidiBuffer const& buffer) override;
virtual std::string friendlyProgramName(MidiProgramNumber programNo) const; //TODO this looks like a capability
virtual std::string setupHelpText() const;

// Internal workings of the Generic Adaptation module
bool pythonModuleHasFunction(std::string const &functionName) const;
bool isFromFile() const;
std::string getSourceFilePath() const;
void reloadPython();

// Call this once before using any other function
static void startupGenericAdaptation();
Expand Down

0 comments on commit 4aa6c5d

Please sign in to comment.