mirror of
https://codeberg.org/armin/rmx-19.git
synced 2026-10-11 16:51:55 +02:00
Add delay tempo sync, rack faceplate, README, docs
- Add host-tempo-synced delay: FX screen gains a 4th SYNC row for rhythmic divisions (1/16 .. 1/1) that lock delay time to DAW tempo - Embed rack faceplate background (b2.png) via juce binary data - Add offline dev probe (rmx_probe) for GUI-less MIDI testing - Allow overriding JUCE_ROOT when configuring the build - Write comprehensive README describing the instrument, UI, MIDI, DSP, and build/install steps
This commit is contained in:
parent
d63db3af8a
commit
2e870380d4
17 changed files with 1413 additions and 324 deletions
|
|
@ -9,6 +9,9 @@ RackEditor::RackEditor (RMX19AudioProcessor& p)
|
|||
{
|
||||
face = juce::Colour (0xff16130f);
|
||||
|
||||
backgroundImage = juce::ImageFileFormat::loadFrom (BinaryData::b2_png,
|
||||
BinaryData::b2_pngSize);
|
||||
|
||||
up = new NavButton (-1, "^", "UP");
|
||||
down = new NavButton (1, "v", "DOWN");
|
||||
left = new NavButton (-1, "<", "LEFT");
|
||||
|
|
@ -30,8 +33,43 @@ RackEditor::RackEditor (RMX19AudioProcessor& p)
|
|||
|
||||
addAndMakeVisible (display);
|
||||
|
||||
scaleBox.addItem ("50%", 1);
|
||||
scaleBox.addItem ("75%", 2);
|
||||
scaleBox.addItem ("100%", 3);
|
||||
scaleBox.addItem ("125%", 4);
|
||||
scaleBox.addItem ("150%", 5);
|
||||
scaleBox.addItem ("200%", 6);
|
||||
scaleBox.addItem ("250%", 7);
|
||||
scaleBox.addItem ("300%", 8);
|
||||
scaleBox.setSelectedId (5, juce::dontSendNotification);
|
||||
scaleBox.setJustificationType (juce::Justification::centred);
|
||||
scaleBox.setTextWhenNothingSelected ("UI scale");
|
||||
scaleBox.setColour (juce::ComboBox::backgroundColourId, juce::Colour (0xff221d18));
|
||||
scaleBox.setColour (juce::ComboBox::outlineColourId, juce::Colour (0xff6b5f4e));
|
||||
scaleBox.setColour (juce::ComboBox::textColourId, juce::Colour (0xffd8cdb9));
|
||||
scaleBox.setColour (juce::ComboBox::arrowColourId, juce::Colour (0xffd8cdb9));
|
||||
scaleBox.setColour (juce::PopupMenu::backgroundColourId, juce::Colour (0xff1c1813));
|
||||
scaleBox.setColour (juce::PopupMenu::textColourId, juce::Colour (0xffd8cdb9));
|
||||
scaleBox.setColour (juce::PopupMenu::highlightedBackgroundColourId, juce::Colour (0xff3a332b));
|
||||
scaleBox.onChange = [this]
|
||||
{
|
||||
switch (scaleBox.getSelectedId())
|
||||
{
|
||||
case 1: setScale (0.5f); break;
|
||||
case 2: setScale (0.75f); break;
|
||||
case 3: setScale (1.0f); break;
|
||||
case 4: setScale (1.25f); break;
|
||||
case 5: setScale (1.5f); break;
|
||||
case 6: setScale (2.0f); break;
|
||||
case 7: setScale (2.5f); break;
|
||||
case 8: setScale (3.0f); break;
|
||||
default: break;
|
||||
}
|
||||
};
|
||||
addAndMakeVisible (scaleBox);
|
||||
|
||||
setWantsKeyboardFocus (true);
|
||||
setSize (656, 248);
|
||||
setScale (1.5f);
|
||||
startTimerHz (18);
|
||||
}
|
||||
|
||||
|
|
@ -42,11 +80,41 @@ RackEditor::~RackEditor()
|
|||
|
||||
void RackEditor::paint (juce::Graphics& g)
|
||||
{
|
||||
const auto b = getLocalBounds();
|
||||
// full-area backdrop so scaled edges stay covered
|
||||
g.fillAll (juce::Colour (0xff0d0b09));
|
||||
|
||||
// --- metal rack face ---
|
||||
g.setColour (face);
|
||||
g.fillRect (b);
|
||||
const auto b = juce::Rectangle<int> (0, 0, baseW, baseH);
|
||||
|
||||
// --- metal rack face (b2.png background, cover-scaled) ---
|
||||
if (backgroundImage.isValid())
|
||||
{
|
||||
const float imageAspect = (float) backgroundImage.getWidth() / (float) backgroundImage.getHeight();
|
||||
const float boxAspect = (float) b.getWidth() / (float) b.getHeight();
|
||||
|
||||
int sx = 0, sy = 0, sw = backgroundImage.getWidth(), sh = backgroundImage.getHeight();
|
||||
if (imageAspect > boxAspect)
|
||||
{
|
||||
sw = (int) (backgroundImage.getHeight() * boxAspect);
|
||||
sx = (backgroundImage.getWidth() - sw) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
sh = (int) (backgroundImage.getWidth() / boxAspect);
|
||||
sy = (backgroundImage.getHeight() - sh) / 2;
|
||||
}
|
||||
g.drawImage (backgroundImage, b.getX(), b.getY(),
|
||||
juce::roundToInt (b.getWidth() * uiScale),
|
||||
juce::roundToInt (b.getHeight() * uiScale),
|
||||
sx, sy, sw, sh);
|
||||
}
|
||||
else
|
||||
{
|
||||
g.setColour (face);
|
||||
g.fillRect (b);
|
||||
}
|
||||
|
||||
g.setColour (juce::Colour (0xff08));
|
||||
g.addTransform (juce::AffineTransform::scale (uiScale));
|
||||
|
||||
// brushed texture
|
||||
g.setColour (juce::Colour::fromRGBA (255, 255, 255, 7));
|
||||
|
|
@ -61,70 +129,62 @@ void RackEditor::paint (juce::Graphics& g)
|
|||
g.fillRect (0, 4, b.getWidth(), 1);
|
||||
g.fillRect (0, b.getHeight() - 5, b.getWidth(), 1);
|
||||
|
||||
// screw holes
|
||||
const auto screw = [&] (int x, int y)
|
||||
{
|
||||
g.setColour (juce::Colour (0xff2a2620));
|
||||
g.fillEllipse (x - 4, y - 4, 8, 8);
|
||||
g.setColour (juce::Colour (0xff8a7f6e));
|
||||
g.drawEllipse (x - 4.5f, y - 4.5f, 9, 9, 1.0f);
|
||||
g.drawLine (x - 3, y, x + 3, y, 1.0f);
|
||||
};
|
||||
screw (8, 8); screw (b.getWidth() - 9, 8);
|
||||
screw (8, b.getHeight() - 9); screw (b.getWidth() - 9, b.getHeight() - 9);
|
||||
|
||||
// brand plate
|
||||
g.setColour (juce::Colour (0xff614f3a));
|
||||
g.fillRect (12, 6, 150, 12);
|
||||
g.setColour (juce::Colour (0xffc9a86f));
|
||||
g.setFont (juce::FontOptions ("Arial", 10.0f, juce::Font::bold));
|
||||
g.drawFittedText ("RMX-19 RACK SYNTH", 12, 6, 150, 12, juce::Justification::centred, 1);
|
||||
g.drawFittedText ("RMX-19", 12, 6, 150, 12, juce::Justification::centred, 1);
|
||||
|
||||
// power LED (right of LCD status)
|
||||
const int ledX = getWidth() - 20, ledY = 18;
|
||||
g.setColour (juce::Colour (0xff3a0d0d));
|
||||
g.fillEllipse (ledX - 3, ledY - 3, 8, 8);
|
||||
g.setColour (juce::Colour::fromFloatRGBA (0.95f, 0.25f + 0.1f * powerPulse, 0.1f, 1.0f));
|
||||
g.fillEllipse (ledX - 2, ledY - 2, 6, 6);
|
||||
g.setColour (juce::Colour (0xff8a7f6e));
|
||||
g.setFont (juce::FontOptions ("Arial", 8.0f, juce::Font::plain));
|
||||
g.drawFittedText ("PWR", ledX - 26, ledY - 5, 20, 10, juce::Justification::centredRight, 1);
|
||||
|
||||
// rear engraving text
|
||||
// rear engraving text (kept clear of the UI-scale dropdown at the bottom right)
|
||||
g.setColour (juce::Colour (0xff6e6657));
|
||||
g.setFont (juce::FontOptions ("Courier New", 8.0f, juce::Font::plain));
|
||||
const juce::String ser = "S/N 0001-1943 MIDI OUT (R) MIDI IN (R) AUDIO 24BIT/96K";
|
||||
g.drawFittedText (ser, 12, getHeight() - 16, getWidth() - 24, 11, juce::Justification::centred, 1);
|
||||
g.drawFittedText (ser, 12, b.getHeight() - 16, b.getWidth() - 128, 11, juce::Justification::centred, 1);
|
||||
}
|
||||
|
||||
void RackEditor::resized()
|
||||
{
|
||||
const auto b = getLocalBounds();
|
||||
// UI-scale dropdown lives in real (unscaled) coordinates, bottom right.
|
||||
scaleBox.setBounds (getWidth() - 112, getHeight() - 30, 104, 20);
|
||||
|
||||
// everything else renders in base 656x248 space, scaled by an affine transform
|
||||
const juce::AffineTransform tf = juce::AffineTransform::scale (uiScale);
|
||||
|
||||
// LCD recessed module
|
||||
const juce::Rectangle<int> lcdArea (14, 24, 356, 168);
|
||||
display.setTransform (tf);
|
||||
display.setBounds (lcdArea);
|
||||
|
||||
// nav diamond + enter/back to the right of the LCD
|
||||
const juce::Rectangle<int> navArea (lcdArea.getRight() + 16, 26, b.getWidth() - lcdArea.getRight() - 30, 164);
|
||||
const juce::Rectangle<int> navArea (lcdArea.getRight() + 16, 26, baseW - lcdArea.getRight() - 30, 164);
|
||||
|
||||
const int bw = 58, bh = 20;
|
||||
const int cx = navArea.getX() + (navArea.getWidth() - bw) / 2;
|
||||
const int rowY = navArea.getY();
|
||||
|
||||
up->setBounds (cx, rowY, bw, bh);
|
||||
left->setBounds (navArea.getX() + 2, rowY + bh + 6, bw, bh);
|
||||
down->setBounds (cx, rowY + bh + 6, bw, bh);
|
||||
right->setBounds (navArea.getRight() - bw - 2, rowY + bh + 6, bw, bh);
|
||||
up->setTransform (tf); up->setBounds (cx + 6, rowY + 1, bw + 3, bh);
|
||||
left->setTransform (tf); left->setBounds (navArea.getX() + 1, rowY + bh + 6, bw + 2, bh);
|
||||
down->setTransform (tf); down->setBounds (cx + 6, rowY + bh + 6 + 1, bw + 3, bh);
|
||||
right->setTransform (tf); right->setBounds (navArea.getRight() - bw + 3, rowY + bh + 6, bw - 3, bh);
|
||||
|
||||
enterB->setBounds (navArea.getX() + 2, rowY + 2 * (bh + 6) + 4, bw, bh);
|
||||
backB->setBounds (navArea.getRight() - bw - 2, rowY + 2 * (bh + 6) + 4, bw, bh);
|
||||
enterB->setTransform (tf); enterB->setBounds (navArea.getX() + 2, rowY + 2 * (bh + 6) + 4, bw + 2, bh);
|
||||
backB->setTransform (tf); backB->setBounds (navArea.getRight() - bw + 3, rowY + 2 * (bh + 6) + 4, bw - 3, bh);
|
||||
}
|
||||
|
||||
void RackEditor::setScale (float s)
|
||||
{
|
||||
uiScale = s;
|
||||
setSize (juce::roundToInt (baseW * s), juce::roundToInt (baseH * s));
|
||||
resized(); // ensure child layouts refresh at the new size
|
||||
}
|
||||
|
||||
void RackEditor::timerCallback()
|
||||
{
|
||||
powerPulse = 0.5f + 0.5f * std::sin (juce::Time::getMillisecondCounter() * 0.0022f);
|
||||
repaint (getLocalBounds());
|
||||
// only the pulsing power LED needs redraws; everything else is static
|
||||
const int ledX = getWidth() - 20, ledY = 18;
|
||||
repaint (ledX - 26, ledY - 7, 30, 14);
|
||||
}
|
||||
|
||||
bool RackEditor::keyPressed (const juce::KeyPress& k)
|
||||
|
|
@ -193,4 +253,4 @@ void RackEditor::OkButton::paintButton (juce::Graphics& g, bool over, bool down)
|
|||
g.setColour (over ? juce::Colour (0xffffd977) : juce::Colour (0xffd8d2c6));
|
||||
g.setFont (juce::FontOptions ("Arial", 9.0f, juce::Font::bold));
|
||||
g.drawFittedText (getName(), b, juce::Justification::centred, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue