Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/element/audioengine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class AudioEngine final : public juce::ReferenceCountedObject {
void setPlaying (const bool shouldBePlaying);
void setRecording (const bool shouldBeRecording);
void seekToAudioFrame (const int64_t frame);
void setMeter (int beatsPerBar, int beatDivisor);
void setMeter (int beatsPerBar, int beatType);

void togglePlayPause();

Expand Down
2 changes: 1 addition & 1 deletion include/element/transport.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class Transport : public Shuttle {

/** Requests a time signature change (thread-safe).
@param beatsPerBar Numerator of time signature
@param beatType Denominator of time signature
@param beatType Beat type enum
*/
void requestMeter (int beatsPerBar, int beatType);

Expand Down
4 changes: 2 additions & 2 deletions src/engine/audioengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -925,10 +925,10 @@ Transport::MonitorPtr AudioEngine::getTransportMonitor() const
return (priv != nullptr) ? priv->transport.getMonitor() : nullptr;
}

void AudioEngine::setMeter (int beatsPerBar, int beatDivisor)
void AudioEngine::setMeter (int beatsPerBar, int beatType)
{
auto& transport (priv->transport);
transport.requestMeter (beatsPerBar, beatDivisor);
transport.requestMeter (beatsPerBar, beatType);
}

void AudioEngine::togglePlayPause()
Expand Down
13 changes: 6 additions & 7 deletions src/engine/transport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,19 +138,18 @@ void Transport::postProcess (int nframes)
}
}

void Transport::requestMeter (int beatsPerBar, int beatDivisor)
void Transport::requestMeter (int beatsPerBar, int beatType)
{
// std::clog << "request meter: " << beatsPerBar << "/" << (1 << beatDivisor) << std::endl;
if (beatsPerBar < 1)
beatsPerBar = 1;
if (beatsPerBar > 99)
beatsPerBar = 99;
if (beatDivisor < 0)
beatDivisor = 0;
if (beatDivisor > BeatType::SixteenthNote)
beatDivisor = BeatType::SixteenthNote;
if (beatType < 0)
beatType = 0;
if (beatType > BeatType::SixteenthNote)
beatType = BeatType::SixteenthNote;
nextBeatsPerBar.set (beatsPerBar);
nextBeatDivisor.set (beatDivisor);
nextBeatDivisor.set (beatType);
}

void Transport::requestAudioFrame (const int64_t frame)
Expand Down
33 changes: 32 additions & 1 deletion src/nodes/midisetlist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
// SPDX-License-Identifier: GPL-3.0-or-later

#include <element/context.hpp>
#include <element/audioengine.hpp>

#include "nodes/midisetlist.hpp"
#include "engine/trace.hpp"
#include "tempo.hpp"

using namespace juce;

Expand Down Expand Up @@ -111,6 +113,16 @@ void MidiSetListProcessor::maybeSendTempoAndPosition (int program)
{
if (entry->tempo >= 20.0)
s->getValueTree().setProperty (tags::tempo, entry->tempo, nullptr);

if (entry->tsNum > 0 && entry->tsDen > 0)
{
const int div = BeatType::fromDivisor (entry->tsDen); // raw denom -> BeatType enum
s->getValueTree().setProperty (tags::beatsPerBar, entry->tsNum, nullptr);
s->getValueTree().setProperty (tags::beatDivisor, div, nullptr);
if (auto e = _context.audio())
e->setMeter (entry->tsNum, div);
}

if (auto e = _context.audio())
e->seekToAudioFrame (0);
}
Expand Down Expand Up @@ -148,6 +160,8 @@ void MidiSetListProcessor::addProgramEntry (const String& name, int programIn, i
entry->in = programIn;
entry->out = programOut;
entry->tempo = 0.0;
entry->tsNum = 0;
entry->tsDen = 0;
sendChangeMessage();

ScopedLock sl (lock);
Expand All @@ -158,15 +172,32 @@ void MidiSetListProcessor::editProgramEntry (int index,
const String& name,
int inProgram,
int outProgram,
double tempo)
double tempo,
int tsNum,
int tsDen)
{
tempo = (tempo <= 0) ? 0.0 : std::max (20.0, std::min (999.0, tempo));

// A valid signature needs both a numerator and a power-of-two divisor; otherwise unset (0/0).
if (tsNum <= 0 || tsDen <= 0)
{
tsNum = tsDen = 0;
}
else
{
tsNum = jlimit (1, 99, tsNum);
// Snap the denominator to the nearest supported divisor via the BeatType enum.
tsDen = (int) BeatType ((BeatType::ID) BeatType::fromDivisor (tsDen)).divisor();
}

if (auto* entry = entries[index])
{
entry->name = name.isNotEmpty() ? name : entry->name;
entry->in = inProgram;
entry->out = outProgram;
entry->tempo = tempo;
entry->tsNum = tsNum;
entry->tsDen = tsDen;
ScopedLock sl (lock);
programMap[entry->in] = entry->out;
sendChangeMessage();
Expand Down
12 changes: 10 additions & 2 deletions src/nodes/midisetlist.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class MidiSetListProcessor : public MidiFilterNode,
int in;
int out;
double tempo { 0.0 };
int tsNum { 0 };
int tsDen { 0 };
};

MidiSetListProcessor (Context&);
Expand Down Expand Up @@ -59,7 +61,9 @@ class MidiSetListProcessor : public MidiFilterNode,
const juce::String& name,
int inProgram,
int outProgram,
double tempo);
double tempo,
int tsNum = 0,
int tsDen = 0);
ProgramEntry getProgramEntry (int index) const;

inline int getWidth() const { return width; }
Expand Down Expand Up @@ -103,6 +107,8 @@ class MidiSetListProcessor : public MidiFilterNode,
entry->in = (int) e["in"];
entry->out = (int) e["out"];
entry->tempo = (double) e["tempo"];
entry->tsNum = (int) e["tsNum"];
entry->tsDen = (int) e["tsDen"];
}

{
Expand All @@ -127,7 +133,9 @@ class MidiSetListProcessor : public MidiFilterNode,
e.setProperty ("name", entry->name, nullptr)
.setProperty ("in", entry->in, nullptr)
.setProperty ("out", entry->out, nullptr)
.setProperty ("tempo", entry->tempo, nullptr);
.setProperty ("tempo", entry->tempo, nullptr)
.setProperty ("tsNum", entry->tsNum, nullptr)
.setProperty ("tsDen", entry->tsDen, nullptr);
tree.appendChild (e, nullptr);
}

Expand Down
97 changes: 94 additions & 3 deletions src/nodes/midisetlisteditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

#include "nodes/midisetlist.hpp"
#include "nodes/midisetlisteditor.hpp"
#include "ui/buttons.hpp"
#include "ui/viewhelpers.hpp"
#include "tempo.hpp"

namespace element {

Expand Down Expand Up @@ -168,6 +170,69 @@ class MidiSetListProgramTempoLabel : public Label
int row = -1;
};

class MidiSetListProgramSignatureWidget : public TimeSignatureSetting
{
public:
MidiSetListProgramSignatureWidget (MidiSetListEditor& e)
: editor (e) {}

~MidiSetListProgramSignatureWidget() {}

void setRow (int r) { row = r; }

void setSelected (bool s)
{
if (selected != s)
{
selected = s;
repaint();
}
}

/** Populate the widget from a program entry, defaulting to 4/4 when unset. */
void setSignature (int tsNum, int tsDen)
{
const int bpb = tsNum > 0 ? tsNum : 4;
const int div = tsDen > 0 ? BeatType::fromDivisor (tsDen)
: (int) BeatType::QuarterNote;
updateMeter (bpb, div, false);
}

/** Render like the surrounding table cells (transparent, light text over the
row background) instead of the base class's boxed look. */
void paint (Graphics& g) override
{
String text;
text << getBeatsPerBar() << " / "
<< (int) BeatType ((BeatType::ID) getBeatDivisor()).divisor();
g.setFont (Font (FontOptions (editor.getFontSize())));
ViewHelpers::drawBasicTextRow (text, g, getWidth(), getHeight(), selected, 0, Justification::centred);
}

void mouseDown (const MouseEvent& ev) override
{
editor.selectRow (row);
TimeSignatureSetting::mouseDown (ev);
}

protected:
void meterChanged() override
{
if (row < 0)
return;

auto program = editor.getProgram (row);
program.tsNum = getBeatsPerBar();
program.tsDen = (int) BeatType ((BeatType::ID) getBeatDivisor()).divisor();
editor.setProgram (row, program);
}

private:
MidiSetListEditor& editor;
int row = -1;
bool selected = false;
};

class MSLE::TableModel : public TableListBoxModel
{
public:
Expand All @@ -177,6 +242,7 @@ class MSLE::TableModel : public TableListBoxModel
InProgram = 1,
Name,
Tempo,
Signature,
OutProgram
};

Expand Down Expand Up @@ -210,7 +276,12 @@ class MSLE::TableModel : public TableListBoxModel
text = String (1 + program.in);
break;
case TableModel::Tempo:
text = String (120.00, 2) + " bpm";
text = program.tempo >= 20.0 ? String (program.tempo, 2) + " bpm" : "N/A";
break;
case TableModel::Signature:
text = (program.tsNum > 0 && program.tsDen > 0)
? String (program.tsNum) + "/" + String (program.tsDen)
: "N/A";
break;
case TableModel::OutProgram:
text = String (1 + program.out);
Expand Down Expand Up @@ -266,6 +337,17 @@ class MSLE::TableModel : public TableListBoxModel
label = t;
break;
}

case TableModel::Signature: {
auto* sig = existing == nullptr
? new MidiSetListProgramSignatureWidget (editor)
: dynamic_cast<MidiSetListProgramSignatureWidget*> (existing);
sig->setSignature (program.tsNum, program.tsDen);
sig->setRow (rowNumber);
sig->setSelected (isRowSelected);
sig->repaint();
return sig;
}
}

if (label == nullptr)
Expand Down Expand Up @@ -304,6 +386,7 @@ MidiSetListEditor::MidiSetListEditor (const Node& node)
header.addColumn ("IN", TableModel::InProgram, 50, 50, -1, flags, -1);
header.addColumn ("NAME", TableModel::Name, 100, 100, -1, flags, -1);
header.addColumn ("TEMPO", TableModel::Tempo, 70, 70, -1, flags, -1);
header.addColumn ("SIG", TableModel::Signature, 60, 60, -1, flags, -1);
header.addColumn ("OUT", TableModel::OutProgram, 50, 50, -1, flags, -1);
model.reset (new TableModel (*this));
table.setModel (model.get());
Expand Down Expand Up @@ -453,7 +536,9 @@ void MidiSetListEditor::setProgram (int index, MidiSetListProcessor::ProgramEntr
entry.name,
entry.in,
entry.out,
entry.tempo);
entry.tempo,
entry.tsNum,
entry.tsDen);
table.updateContent();
}
}
Expand Down Expand Up @@ -551,10 +636,16 @@ void MidiSetListEditor::updateTableHeaderSizes()
const auto tempoSize = (int) glyphs.getBoundingBox (0, -1, true).getWidth() + 4;
header.setColumnWidth (TableModel::Tempo, tempoSize);

GlyphArrangement sigGlyphs;
sigGlyphs.addLineOfText (Font (FontOptions (getFontSize())), "99 / 16", 0, 0);
const auto sigSize = (int) sigGlyphs.getBoundingBox (0, -1, true).getWidth() + 4;
header.setColumnWidth (TableModel::Signature, sigSize);

// clang-format on
const auto fixedTotalSize = header.getColumnWidth (TableModel::InProgram)
+ header.getColumnWidth (TableModel::OutProgram)
+ header.getColumnWidth (TableModel::Tempo);
+ header.getColumnWidth (TableModel::Tempo)
+ header.getColumnWidth (TableModel::Signature);

header.setColumnWidth (TableModel::Name, table.getWidth() - fixedTotalSize);
// clang-format off
Expand Down
2 changes: 2 additions & 0 deletions src/ui/content.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ class Content::Toolbar : public Component,
tempoBar.setUseExtButton (showExt);
tempoBar.getTempoValue().referTo (session->getPropertyAsValue (tags::tempo));
tempoBar.getExternalSyncValue().referTo (session->getPropertyAsValue (tags::externalSync));
tempoBar.getBeatsPerBarValue().referTo (session->getPropertyAsValue (tags::beatsPerBar));
tempoBar.getBeatDivisorValue().referTo (session->getPropertyAsValue (tags::beatDivisor));
tempoBar.stabilizeWithSession (false);
}

Expand Down
19 changes: 19 additions & 0 deletions src/ui/tempoandmeterbar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class TempoAndMeterBar : public Component,

tempoLabel.tempoValue.addListener (this);
extButton.getToggleStateValue().addListener (this);
beatsPerBarValue.addListener (this);
beatDivisorValue.addListener (this);

meter = std::make_unique<TopMeter> (*this);
addAndMakeVisible (meter.get());
Expand All @@ -34,10 +36,17 @@ class TempoAndMeterBar : public Component,

~TempoAndMeterBar()
{
beatDivisorValue.removeListener (this);
beatsPerBarValue.removeListener (this);
extButton.getToggleStateValue().removeListener (this);
tempoLabel.tempoValue.removeListener (this);
}

/** The meter numerator/divisor values, bound to the session so the widget
tracks external changes (e.g. from the MIDI Set List node). */
Value& getBeatsPerBarValue() { return beatsPerBarValue; }
Value& getBeatDivisorValue() { return beatDivisorValue; }

void resized() override
{
auto r (getLocalBounds());
Expand Down Expand Up @@ -73,6 +82,14 @@ class TempoAndMeterBar : public Component,
{
stabilize();

if (v.refersToSameSourceAs (beatsPerBarValue) || v.refersToSameSourceAs (beatDivisorValue))
{
// notify=false: display-only sync, so we don't re-broadcast to engine/session.
meter->updateMeter ((int) beatsPerBarValue.getValue(),
(int) beatDivisorValue.getValue(),
false);
}

if (extButton.isVisible() && v.refersToSameSourceAs (extButton.getToggleStateValue()))
{
if (extButton.getToggleState())
Expand Down Expand Up @@ -160,6 +177,8 @@ class TempoAndMeterBar : public Component,
Transport::MonitorPtr monitor;
AudioEnginePtr engine;
SessionPtr session;
Value beatsPerBarValue;
Value beatDivisorValue;

void stabilize()
{
Expand Down
Loading