Skip to content

Commit

Permalink
A visual Mod and Pitch wheel on the virtual keyboard
Browse files Browse the repository at this point in the history
Addresses surge-synthesizer#5746

Adds a pitch wheel and mod wheel control to the virtual keyboard
Definitely needs some rendering work but all the plumbing drawing
and layout is in place
  • Loading branch information
baconpaul committed Mar 30, 2022
1 parent 741e261 commit 50e4612
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 7 deletions.
108 changes: 107 additions & 1 deletion src/surge-xt/SurgeSynthEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,83 @@
#include "RuntimeFont.h"
#include <version.h>

struct VKeyboardWheel : public juce::Component
{
std::function<void(int)> onValueChanged = [](int f) {};
bool snapBack{false};
bool unipolar{true};
int range{127};
int value{0};
void paint(juce::Graphics &g) override
{
auto wheelSz = getLocalBounds().reduced(1, 2);
g.setColour(juce::Colour(15, 15, 15));
g.fillRect(wheelSz);
g.setColour(juce::Colour(80, 80, 80));
g.drawRect(wheelSz);

float p = 1.0 * value / range;
if (!unipolar)
p = 1.0 * (value + range) / (2 * range);

// y direction is flipped
p = 1 - p;

int nTicks = 10;
float shift = 1.0 * p / nTicks;

for (int i = 0; i < nTicks; ++i)
{
int lev = 150 - (i + p) * 50.0 / (nTicks);
g.setColour(juce::Colour(lev, lev, lev));
float yp = (i + p * nTicks - floor(p * nTicks)) * wheelSz.getHeight() / nTicks +
wheelSz.getY();
g.drawLine(wheelSz.getX(), yp, wheelSz.getRight(), yp);
}

float cp = wheelSz.getY() + p * wheelSz.getHeight();
auto r = wheelSz.withHeight(3).translated(0, cp - 3);
g.setColour(juce::Colours::yellow);
g.fillRect(r);
}

void valueFromY(float y)
{
auto wheelSz = getLocalBounds().reduced(1, 2);
auto py = std::clamp(y, 1.f * wheelSz.getY(), 1.f * wheelSz.getY() + wheelSz.getHeight());
py = (py - wheelSz.getY()) / wheelSz.getHeight();
py = 1 - py;

if (unipolar)
value = py * range;
else
value = 2 * py * range - range;
onValueChanged(value);
}

void mouseDown(const juce::MouseEvent &event) override
{
valueFromY(event.position.y);
repaint();
}

void mouseDrag(const juce::MouseEvent &event) override
{
valueFromY(event.position.y);
repaint();
}

void mouseUp(const juce::MouseEvent &event) override
{
if (snapBack)
{
value = 0;
onValueChanged(value);
}
repaint();
}
};

//==============================================================================
SurgeSynthEditor::SurgeSynthEditor(SurgeSynthProcessor &p)
: juce::AudioProcessorEditor(&p), processor(p)
Expand All @@ -42,6 +119,23 @@ SurgeSynthEditor::SurgeSynthEditor(SurgeSynthProcessor &p)
// this makes VKB always receive keyboard input (except when we focus on any typeins, of course)
keyboard->setWantsKeyboardFocus(false);

auto w = std::make_unique<VKeyboardWheel>();
w->snapBack = true;
w->unipolar = false;
w->range = 8196;
w->onValueChanged = [this](auto f) {
processor.midiFromGUI.push(
SurgeSynthProcessor::midiR(SurgeSynthProcessor::midiR::PITCHWHEEL, f));
};
pitchwheel = std::move(w);

auto m = std::make_unique<VKeyboardWheel>();
m->onValueChanged = [this](auto f) {
processor.midiFromGUI.push(
SurgeSynthProcessor::midiR(SurgeSynthProcessor::midiR::MODWHEEL, f));
};
modwheel = std::move(m);

tempoTypein = std::make_unique<juce::TextEditor>("Tempo");
tempoTypein->setFont(Surge::GUI::getFontManager()->getLatoAtSize(11));
tempoTypein->setInputRestrictions(3, "0123456789");
Expand All @@ -57,6 +151,8 @@ SurgeSynthEditor::SurgeSynthEditor(SurgeSynthProcessor &p)
tempoLabel = std::make_unique<juce::Label>("Tempo", "Tempo");

addChildComponent(*keyboard);
addChildComponent(*pitchwheel);
addChildComponent(*modwheel);
addChildComponent(*tempoLabel);
addChildComponent(*tempoTypein);

Expand Down Expand Up @@ -184,12 +280,13 @@ void SurgeSynthEditor::resized()
{
auto y = adapter->getWindowSizeY();
auto x = addTempo ? 50 : 0;
auto wheels = 32;
int tempoHeight = 14, typeinHeight = 18, yOffset = -2;
int tempoBlockHeight = tempoHeight + typeinHeight;
int tempoBlockYPos = ((extraYSpaceForVirtualKeyboard - tempoBlockHeight) / 2) + yOffset;

auto xf = juce::AffineTransform().scaled(applyZoomFactor);
auto r = juce::Rectangle<int>(x, y, adapter->getWindowSizeX() - x,
auto r = juce::Rectangle<int>(x + wheels, y, adapter->getWindowSizeX() - x - wheels,
extraYSpaceForVirtualKeyboard);
// std::cout << "B4 " << r.toString() << std::endl;
// r = r.transformedBy(xf);
Expand All @@ -198,6 +295,15 @@ void SurgeSynthEditor::resized()
keyboard->setTransform(xf); // juce::AffineTransform().scaled(1.05));
keyboard->setVisible(true);

auto pmr = juce::Rectangle<int>(x, y, wheels / 2, extraYSpaceForVirtualKeyboard);
pitchwheel->setBounds(pmr);
pitchwheel->setTransform(xf); // juce::AffineTransform().scaled(1.05));
pitchwheel->setVisible(true);
pmr = pmr.translated(wheels / 2, 0);
modwheel->setBounds(pmr);
modwheel->setTransform(xf); // juce::AffineTransform().scaled(1.05));
modwheel->setVisible(true);

if (addTempo)
{
tempoLabel->setBounds(4, y + tempoBlockYPos, x - 8, tempoHeight);
Expand Down
1 change: 1 addition & 0 deletions src/surge-xt/SurgeSynthEditor.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class SurgeSynthEditor : public juce::AudioProcessorEditor,
bool drawExtendedControls{false};
int midiKeyboardOctave{5};
float midiKeyboardVelocity{96.f / 127.f};
std::unique_ptr<juce::Component> pitchwheel, modwheel;
std::unique_ptr<juce::MidiKeyboardComponent> keyboard;
std::unique_ptr<juce::Label> tempoLabel;
std::unique_ptr<juce::TextEditor> tempoTypein;
Expand Down
19 changes: 15 additions & 4 deletions src/surge-xt/SurgeSynthProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,21 @@ void SurgeSynthProcessor::processBlock(juce::AudioBuffer<float> &buffer,
midiR rec;
while (midiFromGUI.pop(rec))
{
if (rec.on)
surge->playNote(rec.ch, rec.note, rec.vel, 0);
else
surge->releaseNote(rec.ch, rec.note, rec.vel);
if (rec.type == midiR::NOTE)
{
if (rec.on)
surge->playNote(rec.ch, rec.note, rec.vel, 0);
else
surge->releaseNote(rec.ch, rec.note, rec.vel);
}
if (rec.type == midiR::PITCHWHEEL)
{
surge->pitchBend(rec.ch, rec.cval);
}
if (rec.type == midiR::MODWHEEL)
{
surge->channelController(rec.ch, 1, rec.cval);
}
}

// Make sure we have a main output
Expand Down
12 changes: 10 additions & 2 deletions src/surge-xt/SurgeSynthProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,18 @@ class SurgeSynthProcessor : public juce::AudioProcessor,
std::atomic<float> standaloneTempo{120};
struct midiR
{
midiR() {}
midiR(int c, int n, int v, bool o) : ch(c), note(n), vel(v), on(o) {}
enum Type
{
NOTE,
MODWHEEL,
PITCHWHEEL,
} type{NOTE};
int ch{0}, note{0}, vel{0};
bool on{false};
int cval{0};
midiR() {}
midiR(int c, int n, int v, bool o) : type(NOTE), ch(c), note(n), vel(v), on(o) {}
midiR(Type type, int cval) : type(type), cval(cval) {}
};
LockFreeStack<midiR, 4096> midiFromGUI;
bool isAddingFromMidi{false};
Expand Down

0 comments on commit 50e4612

Please sign in to comment.