diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d32226..7e89c1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,7 @@ target_compile_definitions(${PROJECT_NAME} PUBLIC target_link_libraries(${PROJECT_NAME} PUBLIC juce::juce_graphics + juce::juce_animation juce::juce_audio_utils juce::juce_audio_devices oddsound-mts-source @@ -96,4 +97,4 @@ target_link_libraries(${PROJECT_NAME} PRIVATE # lattices-assets ) -include(cmake/basic-installer.cmake) \ No newline at end of file +include(cmake/basic-installer.cmake) diff --git a/src/JIMath.h b/src/JIMath.h index a483624..c08e6f2 100644 --- a/src/JIMath.h +++ b/src/JIMath.h @@ -197,43 +197,6 @@ struct JIMath octaveReduceRatio(n, d); ratioToMonzo(n, d, m); } - - // ============ Note Name Support - - std::string nameNoteOnLattice(int x, int y) - { - auto location = x + y * 4 + 3; - - auto ml = ((location % 7) + 7) % 7; - std::string name = noteNames[ml]; - - while (location >= 7) - { - name += "#"; - location -= 7; - } - while (location < 0) - { - name += "b"; - location += 7; - } - auto pom = y; - while (pom > 0) - { - name += "-"; - --pom; - } - while (pom < 0) - { - name += "+"; - ++pom; - } - - return name; - } - - private: - std::string noteNames[7] = {"F", "C", "G", "D", "A", "E", "B"}; }; #endif // JI_MTS_SOURCE_JIMATH_H diff --git a/src/LatticeComponent.h b/src/LatticeComponent.h index 29c15ac..5a1c726 100644 --- a/src/LatticeComponent.h +++ b/src/LatticeComponent.h @@ -21,10 +21,11 @@ #include #include +#include #include //============================================================================== -struct LatticeComponent : juce::Component, private juce::Timer +struct LatticeComponent : juce::Component, private juce::MultiTimer { LatticeComponent(LatticesProcessor &p) : proc(&p) { @@ -53,8 +54,18 @@ struct LatticeComponent : juce::Component, private juce::Timer addAndMakeVisible(*southButton); southButton->onClick = [this] { proc->shift(4); }; + zoomOutButton = std::make_unique("-"); + addAndMakeVisible(*zoomOutButton); + zoomOutButton->onClick = [this] { zoomOut(); }; + + zoomInButton = std::make_unique("+"); + addAndMakeVisible(*zoomInButton); + zoomInButton->onClick = [this] { zoomIn(); }; + + updater.addAnimator(follow); setWantsKeyboardFocus(true); - startTimer(50); + startTimer(0, 50); // for keyboard gestures + startTimer(1, 50); // for following the highlight around } LatticeComponent(std::pair *c, int *v) : proc(nullptr) @@ -103,6 +114,9 @@ struct LatticeComponent : juce::Component, private juce::Timer eastButton->setBounds(b.getRight() - 38, b.getBottom() - 71, 24, 24); northButton->setBounds(b.getRight() - 71, b.getBottom() - 104, 24, 24); southButton->setBounds(b.getRight() - 71, b.getBottom() - 38, 24, 24); + + zoomOutButton->setBounds(20, b.getBottom() - 55, 35, 35); + zoomInButton->setBounds(60, b.getBottom() - 55, 35, 35); } bool keyPressed(const juce::KeyPress &key) override @@ -144,16 +158,43 @@ struct LatticeComponent : juce::Component, private juce::Timer return false; } - void timerCallback() override + void timerCallback(int timerID) override { - if (westFlag) + if (timerID == 0) + { westFlag = false; - if (eastFlag) eastFlag = false; - if (northFlag) northFlag = false; - if (southFlag) southFlag = false; + } + + if (timerID == 1) + { + if (proc->changed) + { + repaint(); + proc->changed = false; + + int nx = proc->positionX; + int ny = proc->positionY; + + bool sH = (procX != nx && nx % 4 == 0); + bool sV = (procY != ny && ny % 3 == 0); + + procX = nx; + procY = ny; + + if (sH || sV) + { + priorX = xShift; + priorY = yShift; + goalX = procX; + goalY = procY; + + follow.start(); + } + } + } } void paint(juce::Graphics &g) override @@ -165,34 +206,38 @@ struct LatticeComponent : juce::Component, private juce::Timer float ctrDistance{JIRadius * (5.f / 3.f)}; - float vDistance = 2.0f * ctrDistance; - float hDistance = 2.0f * ctrDistance; + float vhDistance = 2.0f * ctrDistance; auto ctrX = getWidth() / 2; auto ctrH = getHeight() / 2; - auto nV = std::ceil(getHeight() / vDistance); - auto nW = std::ceil(getWidth() / hDistance); + int yS = static_cast(std::abs(yShift)); + int xS = static_cast(std::abs(xShift)); + + int nV = std::ceil(getHeight() / vhDistance) + yS; + int nW = std::ceil(getWidth() / vhDistance) + xS; int dist{0}, hDist{0}, uDist{0}, dDist{0}; juce::Image Lines{juce::Image::ARGB, getWidth(), getHeight(), true}; juce::Image Spheres{juce::Image::ARGB, getWidth(), getHeight(), true}; + juce::Image Text{juce::Image::ARGB, getWidth(), getHeight(), true}; { juce::Graphics lG(Lines); juce::Graphics sG(Spheres); - for (int v = -nV - 1; v < nV + 1; ++v) + juce::Graphics tG(Text); + for (int v = -nV; v < nV; ++v) { - float off = v * hDistance * 0.5f; - float y = -v * vDistance + ctrH; - if (y < -vDistance || y > getHeight() + vDistance) + float off = v * vhDistance * 0.5f; + float y = -v * vhDistance + ctrH + yShift; + if (y < -ctrDistance || y > getHeight() + ctrDistance) continue; - for (int w = -nW - 1; w < nW + 1; ++w) + for (int w = -nW; w < nW; ++w) { - float x = w * hDistance + ctrX + off; + float x = w * vhDistance + ctrX + off - xShift; - if (x < -hDistance || x > getWidth() + hDistance) + if (x < -ctrDistance || x > getWidth() + ctrDistance) continue; int degree{0}; @@ -238,20 +283,20 @@ struct LatticeComponent : juce::Component, private juce::Timer // Horizontal Line alpha = 1.f / (std::sqrt(hDist) + 1); lG.setColour(juce::Colours::ghostwhite.withAlpha(alpha)); - juce::Line horiz(x, y, x + hDistance, y); + juce::Line horiz(x, y, x + vhDistance, y); lG.drawLine(horiz, thickness); // Upward Line alpha = 1.f / (std::sqrt(uDist) + 1); lG.setColour(juce::Colours::ghostwhite.withAlpha(alpha)); - juce::Line up(x, y, x + (hDistance * .5f), y - vDistance); + juce::Line up(x, y, x + (vhDistance * .5f), y - vhDistance); float ul[2] = {7.f, 3.f}; lG.drawDashedLine(up, ul, 2, thickness, 1); // Downward Line alpha = 1.f / (std::sqrt(dDist) + 1); lG.setColour(juce::Colours::ghostwhite.withAlpha(alpha)); - juce::Line down(x, y, x + (hDistance * .5f), y + vDistance); + juce::Line down(x, y, x + (vhDistance * .5f), y + vhDistance); float dl[2] = {2.f, 3.f}; lG.drawDashedLine(down, dl, 2, thickness, 1); @@ -285,17 +330,20 @@ struct LatticeComponent : juce::Component, private juce::Timer thickness); // Names or Ratios? + /* auto [n, d] = calculateCell(w, v); if (enabled && (dist == 0 && proc->currentVisitors->vis[degree] > 1)) - { reCalculateCell(n, d, proc->currentVisitors->vis[degree], degree); } auto s = std::to_string(n) + "/" + std::to_string(d); - // std::string s = jim.nameNoteOnLattice(w, v); - sG.setFont(stoke); - sG.drawFittedText(s, x - ellipseRadius + 3, y - (JIRadius / 3.f), + */ + + std::string s = nameNoteOnLattice(w, v); + tG.setColour(juce::Colours::ghostwhite.withAlpha(alpha)); + tG.setFont(stoke); + tG.drawFittedText(s, x - ellipseRadius + 3, y - (JIRadius / 3.f), 2.f * (ellipseRadius - 3), .66667f * JIRadius, juce::Justification::horizontallyCentred, 1, 0.05f); } @@ -303,14 +351,76 @@ struct LatticeComponent : juce::Component, private juce::Timer } g.drawImageAt(Lines, 0, 0, false); g.drawImageAt(Spheres, 0, 0, false); + g.drawImageAt(Text, 0, 0, false); auto b = this->getLocalBounds(); + g.setColour(bg); g.fillRect(b.getRight() - 110, b.getBottom() - 110, 101, 101); g.setColour(juce::Colours::ghostwhite); g.drawRect(b.getRight() - 110, b.getBottom() - 110, 101, 101); } + private: + int syntonicDrift{0}, diesisDrift{0}, procX{0}, procY{0}; + float xShift{0}, yShift{0}, priorX{0}, priorY{0}, goalX{0}, goalY{0}; + + juce::VBlankAnimatorUpdater updater{this}; + juce::Animator follow = + juce::ValueAnimatorBuilder{} + .withEasing(juce::Easings::createEaseInOut()) + .withDurationMs(1000) + .withValueChangedCallback([this](auto value) { shiftToFollow((float)value); }) + .build(); + + void shiftToFollow(const float v) + { + float dist = JIRadius * 2.f * (5.f / 3.f); + auto nv = 1 - v; + + xShift = nv * priorX + v * dist * (goalX + goalY * .5f); + yShift = nv * priorY + v * dist * goalY; + + repaint(); + + // if (follow.isComplete()) + } + + std::string noteNames[7] = {"F", "C", "G", "D", "A", "E", "B"}; + + std::string nameNoteOnLattice(int x, int y) + { + int origin = proc->originNoteName.first + proc->originNoteName.second * 7; + int location = x + y * 4 + origin; + int letter = ((location % 7) + 7) % 7; + std::string name = noteNames[letter]; + + while (location >= 7) + { + name += "#"; + location -= 7; + } + while (location < 0) + { + name += "b"; + location += 7; + } + + auto row = y; + while (row > 0) + { + name += "-"; + --row; + } + while (row < 0) + { + name += "+"; + ++row; + } + + return name; + } + protected: LatticesProcessor *proc; @@ -497,6 +607,9 @@ struct LatticeComponent : juce::Component, private juce::Timer } private: + std::unique_ptr zoomOutButton; + std::unique_ptr zoomInButton; + std::unique_ptr westButton; std::unique_ptr eastButton; std::unique_ptr northButton; @@ -598,14 +711,13 @@ template struct SmallLatticeComponent : LatticeComponent float ctrDistance{JIRadius * (5.f / 3.f)}; - float vDistance = 2.0f * ctrDistance; - float hDistance = 2.0f * ctrDistance; + float vhDistance = 2.0f * ctrDistance; auto ctrX = getWidth() / 2; auto ctrH = getHeight() / 2; - auto nV = std::ceil(getHeight() / vDistance); - auto nW = std::ceil(getWidth() / hDistance); + auto nV = std::ceil(getHeight() / vhDistance); + auto nW = std::ceil(getWidth() / vhDistance); juce::Image Lines{juce::Image::ARGB, getWidth(), getHeight(), true}; juce::Image Spheres{juce::Image::ARGB, getWidth(), getHeight(), true}; @@ -614,14 +726,14 @@ template struct SmallLatticeComponent : LatticeComponent juce::Graphics sG(Spheres); for (int v = -nV; v < nV; ++v) { - float off = v * hDistance * 0.5f; - float y = -v * vDistance + ctrH; + float off = v * vhDistance * 0.5f; + float y = -v * vhDistance + ctrH; if (y < 0 || y > getHeight()) continue; for (int w = -nW; w < nW; ++w) { - float x = w * hDistance + ctrX + off; + float x = w * vhDistance + ctrX + off; if (x < 0 || x > getWidth()) continue; @@ -662,14 +774,14 @@ template struct SmallLatticeComponent : LatticeComponent if (hLit) // Horizontal Line { lG.setColour(juce::Colours::ghostwhite.withAlpha(alpha)); - juce::Line horiz(x, y, x + hDistance, y); + juce::Line horiz(x, y, x + vhDistance, y); lG.drawLine(horiz, thickness); } if (uLit) // Upward Line { lG.setColour(juce::Colours::ghostwhite.withAlpha(alpha)); - juce::Line up(x, y, x + (hDistance * .5f), y - vDistance); + juce::Line up(x, y, x + (vhDistance * .5f), y - vhDistance); float ul[2] = {7.f, 3.f}; lG.drawDashedLine(up, ul, 2, thickness, 1); } @@ -677,7 +789,7 @@ template struct SmallLatticeComponent : LatticeComponent if (dLit) // Downward Line { lG.setColour(juce::Colours::ghostwhite.withAlpha(alpha)); - juce::Line down(x, y, x + (hDistance * .5f), y + vDistance); + juce::Line down(x, y, x + (vhDistance * .5f), y + vhDistance); float dl[2] = {2.f, 3.f}; lG.drawDashedLine(down, dl, 2, thickness, 1); } @@ -715,14 +827,13 @@ template struct SmallLatticeComponent : LatticeComponent sG.drawEllipse(x - ellipseRadius, y - JIRadius, 2 * ellipseRadius, 2 * JIRadius, thickness); - // Names or Ratios? auto [n, d] = calculateCell(w, v); if (dist == 0 && visitor[degree] > 1) { reCalculateCell(n, d, visitor[degree], degree); } auto s = std::to_string(n) + "/" + std::to_string(d); - // std::string s = jim.nameNoteOnLattice(w, v); + sG.setFont(stoke); sG.drawFittedText(s, x - ellipseRadius + 3, y - (JIRadius / 3.f), 2.f * (ellipseRadius - 3), .66667f * JIRadius, diff --git a/src/LatticesEditor.cpp b/src/LatticesEditor.cpp index f16fc9c..6dad8f3 100644 --- a/src/LatticesEditor.cpp +++ b/src/LatticesEditor.cpp @@ -24,11 +24,11 @@ LatticesEditor::LatticesEditor(LatticesProcessor &p) : juce::AudioProcessorEdito if (p.registeredMTS) { - startTimer(0, 5); init(); } else { + latticeComponent->setEnabled(false); startTimer(1, 50); } @@ -46,16 +46,9 @@ void LatticesEditor::init() addAndMakeVisible(*menuComponent); menuComponent->setBounds(this->getLocalBounds()); - zoomOutButton = std::make_unique("-"); - addAndMakeVisible(*zoomOutButton); - zoomOutButton->onClick = [this] { latticeComponent->zoomOut(); }; - - zoomInButton = std::make_unique("+"); - addAndMakeVisible(*zoomInButton); - zoomInButton->onClick = [this] { latticeComponent->zoomIn(); }; - inited = true; startTimer(0, 5); + resized(); } void LatticesEditor::paint(juce::Graphics &g) { g.fillAll(backgroundColour); } @@ -72,7 +65,7 @@ void LatticesEditor::resized() auto h = 30; if (menuComponent->visC->isVisible()) { - h = 300; + h = 330; } else if (menuComponent->settingsC->isVisible()) { @@ -80,8 +73,6 @@ void LatticesEditor::resized() } menuComponent->setBounds(0, 0, b.getWidth(), h); - zoomOutButton->setBounds(20, b.getBottom() - 55, 35, 35); - zoomInButton->setBounds(60, b.getBottom() - 55, 35, 35); } else { @@ -98,6 +89,7 @@ void LatticesEditor::timerCallback(int timerID) warningComponent->setVisible(false); warningComponent->setEnabled(false); stopTimer(1); + latticeComponent->setEnabled(true); init(); } } @@ -128,11 +120,5 @@ void LatticesEditor::timerCallback(int timerID) latticeComponent->repaint(); } latticeComponent->setEnabled(!menuComponent->visC->isVisible()); - - if (processor.changed) - { - latticeComponent->repaint(); - processor.changed = false; - } } } diff --git a/src/LatticesEditor.h b/src/LatticesEditor.h index 5b09185..ffa52b1 100644 --- a/src/LatticesEditor.h +++ b/src/LatticesEditor.h @@ -60,9 +60,6 @@ class LatticesEditor : public juce::AudioProcessorEditor, juce::MultiTimer std::unique_ptr warningComponent; std::unique_ptr menuComponent; - std::unique_ptr zoomOutButton; - std::unique_ptr zoomInButton; - void init(); bool inited{false}; bool menuWasOpen{false}; diff --git a/src/LatticesProcessor.cpp b/src/LatticesProcessor.cpp index 202966d..9273f46 100644 --- a/src/LatticesProcessor.cpp +++ b/src/LatticesProcessor.cpp @@ -56,6 +56,8 @@ LatticesProcessor::LatticesProcessor() returnToOrigin(); startTimer(1, 5); } + + startTimer(2, 50); } LatticesProcessor::~LatticesProcessor() @@ -160,6 +162,57 @@ void LatticesProcessor::setStateInformation(const void *data, int sizeInBytes) listenOnChannel = xmlState->getIntAttribute("channel", 1); originalRefNote = xmlState->getIntAttribute("note", 0); + switch (originalRefNote) + { + case 0: + originNoteName.first = 1; + originNoteName.second = 0; + break; + case 1: + originNoteName.first = 3; + originNoteName.second = -1; + break; + case 2: + originNoteName.first = 3; + originNoteName.second = 0; + break; + case 3: + originNoteName.first = 5; + originNoteName.second = -1; + break; + case 4: + originNoteName.first = 5; + originNoteName.second = 0; + break; + case 5: + originNoteName.first = 0; + originNoteName.second = 0; + break; + case 6: + originNoteName.first = 0; + originNoteName.second = 1; + break; + case 7: + originNoteName.first = 2; + originNoteName.second = 0; + break; + case 8: + originNoteName.first = 4; + originNoteName.second = -1; + break; + case 9: + originNoteName.first = 4; + originNoteName.second = 0; + break; + case 10: + originNoteName.first = 6; + originNoteName.second = -1; + break; + case 11: + originNoteName.first = 6; + originNoteName.second = 0; + break; + } originalRefFreq = xmlState->getDoubleAttribute("freq", 261.6255653005986); maxDistance = xmlState->getIntAttribute("md", 24); @@ -407,6 +460,59 @@ double LatticesProcessor::updateRoot(int r) originalRefNote = r; originalRefFreq = nf; + + switch (r) + { + case 0: + originNoteName.first = 1; + originNoteName.second = 0; + break; + case 1: + originNoteName.first = 3; + originNoteName.second = -1; + break; + case 2: + originNoteName.first = 3; + originNoteName.second = 0; + break; + case 3: + originNoteName.first = 5; + originNoteName.second = -1; + break; + case 4: + originNoteName.first = 5; + originNoteName.second = 0; + break; + case 5: + originNoteName.first = 0; + originNoteName.second = 0; + break; + case 6: + originNoteName.first = 0; + originNoteName.second = 1; + break; + case 7: + originNoteName.first = 2; + originNoteName.second = 0; + break; + case 8: + originNoteName.first = 4; + originNoteName.second = -1; + break; + case 9: + originNoteName.first = 4; + originNoteName.second = 0; + break; + case 10: + originNoteName.first = 6; + originNoteName.second = -1; + break; + case 11: + originNoteName.first = 6; + originNoteName.second = 0; + break; + } + returnToOrigin(); updateHostDisplay(juce::AudioProcessor::ChangeDetails().withNonParameterStateChanged(true)); diff --git a/src/LatticesProcessor.h b/src/LatticesProcessor.h index a1073fe..8901b58 100644 --- a/src/LatticesProcessor.h +++ b/src/LatticesProcessor.h @@ -98,10 +98,12 @@ class LatticesProcessor : public juce::AudioProcessor, int homeCC = 5; int listenOnChannel = 1; - int originalRefNote{-12}; - double originalRefFreq{-1}; - int currentRefNote{}; - double currentRefFreq{}; + int originalRefNote{0}; + double originalRefFreq{261.6255653005986}; + int currentRefNote{0}; + double currentRefFreq{261.6255653005986}; + + std::pair originNoteName = {1, 0}; std::vector visitorGroups; Visitors *currentVisitors;