Skip to content
Open
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
18 changes: 2 additions & 16 deletions framework/audio/common/automatablevalue.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,11 @@

#include "global/types/secs.h"
#include "global/types/number.h"
#include "global/types/sharedmap.h"
#include "global/containers.h"

#include "mpe/automationpoint.h"

namespace muse::audio {
using AutomationEnvelope = SharedMap<muse::secs_t, mpe::AutomationPoint>;
using AutomationEnvelope = mpe::AutomationCurve<muse::secs_t>;

//! NOTE: a parameter that is either a fixed value, or driven by an AutomationEnvelope over time
template<typename T>
Expand All @@ -58,19 +56,7 @@ class AutomatableValue
return T {};
}

auto it = findLessOrEqual(envelope, pos);
if (it == envelope.end()) {
//! NOTE: hold the first point's value backwards in time, matching standard envelope semantics
it = envelope.begin();
}

real_t normalized = it->second.outValue;
const auto next = std::next(it);
if (next != envelope.end()) {
const real_t t = static_cast<real_t>(pos - it->first) / static_cast<real_t>(next->first - it->first);
normalized = mpe::evaluateAt(next->second, it->second.outValue, t);
}

const real_t normalized = mpe::evaluateCurveAt(envelope, pos);
const T mapped = min + static_cast<T>(normalized.raw()) * (max - min);
return std::clamp(mapped, min, max);
}
Expand Down
12 changes: 6 additions & 6 deletions framework/audio/common/rpc/rpcpacker.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ void pack_custom(muse::msgpack::Packer& p, const muse::mpe::PlaybackEvent& value
void unpack_custom(muse::msgpack::UnPacker& p, muse::mpe::PlaybackEvent& value);

// AutomationPoint
void pack_custom(muse::msgpack::Packer& p, const muse::mpe::AutomationPoint::Bend& value);
void unpack_custom(muse::msgpack::UnPacker& p, muse::mpe::AutomationPoint::Bend& value);
void pack_custom(muse::msgpack::Packer& p, const muse::mpe::AutomationPoint::Ease& value);
void unpack_custom(muse::msgpack::UnPacker& p, muse::mpe::AutomationPoint::Ease& value);
void pack_custom(muse::msgpack::Packer& p, const muse::mpe::AutomationPoint::ArrivalFromPrevious& value);
void unpack_custom(muse::msgpack::UnPacker& p, muse::mpe::AutomationPoint::ArrivalFromPrevious& value);
void pack_custom(muse::msgpack::Packer& p, const muse::mpe::AutomationPoint::ExplicitArrival& value);
Expand Down Expand Up @@ -668,12 +668,12 @@ inline void unpack_custom(muse::msgpack::UnPacker& p, muse::mpe::PlaybackEvent&
}
}

inline void pack_custom(muse::msgpack::Packer& p, const muse::mpe::AutomationPoint::Bend& value)
inline void pack_custom(muse::msgpack::Packer& p, const muse::mpe::AutomationPoint::Ease& value)
{
p.process(value.t, value.value);
}

inline void unpack_custom(muse::msgpack::UnPacker& p, muse::mpe::AutomationPoint::Bend& value)
inline void unpack_custom(muse::msgpack::UnPacker& p, muse::mpe::AutomationPoint::Ease& value)
{
p.process(value.t, value.value);
}
Expand All @@ -690,12 +690,12 @@ inline void unpack_custom(muse::msgpack::UnPacker&, muse::mpe::AutomationPoint::

inline void pack_custom(muse::msgpack::Packer& p, const muse::mpe::AutomationPoint::ExplicitArrival& value)
{
p.process(value.value, value.bend);
p.process(value.value, value.ease);
}

inline void unpack_custom(muse::msgpack::UnPacker& p, muse::mpe::AutomationPoint::ExplicitArrival& value)
{
p.process(value.value, value.bend);
p.process(value.value, value.ease);
}

inline void pack_custom(muse::msgpack::Packer& p, const muse::mpe::AutomationPoint::InValue& value)
Expand Down
17 changes: 8 additions & 9 deletions framework/audio/engine/internal/abstracteventsequencer.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class AbstractEventSequencer : public async::Asyncable
m_playbackData = data;

m_playbackData.mainStream.onReceive(this, [this](const mpe::PlaybackEventsMap& events,
const mpe::DynamicLevelLayers& dynamics) {
const mpe::DynamicAutomationLayers& dynamics) {
m_playbackData.originEvents = events;
m_playbackData.dynamics = dynamics;
m_shouldUpdateMainStreamEvents = true;
Expand All @@ -72,13 +72,12 @@ class AbstractEventSequencer : public async::Asyncable
});

m_playbackData.offStream.onReceive(this, [this](const mpe::PlaybackEventsMap& events,
const mpe::DynamicLevelLayers& dynamics,
bool flush) {
if (flush) {
flushOffstream();
}

updateOffStreamEvents(events, dynamics);
updateOffStreamEvents(events);
});

updateMainStreamEvents(data.originEvents, data.dynamics);
Expand Down Expand Up @@ -168,11 +167,11 @@ class AbstractEventSequencer : public async::Asyncable
mpe::dynamic_level_t dynamicLevel(const msecs_t position) const
{
for (const auto& layer : m_playbackData.dynamics) {
const mpe::DynamicLevelMap& dynamics = layer.second;
auto it = muse::findLessOrEqual(dynamics, position);
if (it != dynamics.end()) {
return it->second;
if (layer.second.empty()) {
continue;
}

return mpe::dynamicLevelFromNormalized(mpe::evaluateCurveAt(layer.second, position));
Comment thread
RomanPudashkin marked this conversation as resolved.
}

return mpe::dynamicLevelFromType(muse::mpe::DynamicType::Natural);
Expand Down Expand Up @@ -211,8 +210,8 @@ class AbstractEventSequencer : public async::Asyncable
}

protected:
virtual void updateOffStreamEvents(const mpe::PlaybackEventsMap& events, const mpe::DynamicLevelLayers& dynamics) = 0;
virtual void updateMainStreamEvents(const mpe::PlaybackEventsMap& events, const mpe::DynamicLevelLayers& dynamics) = 0;
virtual void updateMainStreamEvents(const mpe::PlaybackEventsMap& events, const mpe::DynamicAutomationLayers& dynamics) = 0;
virtual void updateOffStreamEvents(const mpe::PlaybackEventsMap& events) = 0;

void resetAllIterators()
{
Expand Down
1 change: 0 additions & 1 deletion framework/audio/engine/internal/nodes/eventaudionode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ EventAudioNode::EventAudioNode(TrackId trackId, const mpe::PlaybackData& playbac

if (onOffStreamReceived) {
m_playbackData.offStream.onReceive(this, [onOffStreamReceived](const PlaybackEventsMap&,
const DynamicLevelLayers&,
bool) {
onOffStreamReceived();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,7 @@ int FluidSequencer::naturalExpressionLevel() const
return NATURAL_EXP_LVL;
}

void FluidSequencer::updateOffStreamEvents(const mpe::PlaybackEventsMap& events, const mpe::DynamicLevelLayers& dynamics)
{
addPlaybackEvents(m_offStreamEvents, events);

if (m_useDynamicEvents) {
addDynamicEvents(m_offStreamEvents, dynamics);
}

updateOffSequenceIterator();
}

void FluidSequencer::updateMainStreamEvents(const mpe::PlaybackEventsMap& events, const mpe::DynamicLevelLayers& dynamics)
void FluidSequencer::updateMainStreamEvents(const mpe::PlaybackEventsMap& events, const mpe::DynamicAutomationLayers& dynamics)
{
m_mainStreamEvents.clear();

Expand All @@ -87,6 +76,12 @@ void FluidSequencer::updateMainStreamEvents(const mpe::PlaybackEventsMap& events
updateMainSequenceIterator();
}

void FluidSequencer::updateOffStreamEvents(const mpe::PlaybackEventsMap& events)
{
addPlaybackEvents(m_offStreamEvents, events);
updateOffSequenceIterator();
}

muse::async::Channel<channel_t, Program> FluidSequencer::channelAdded() const
{
return m_channels.channelAdded;
Expand Down Expand Up @@ -119,16 +114,25 @@ void FluidSequencer::addPlaybackEvents(EventSequenceMap& destination, const mpe:
addSostenutoEvents(destination, sostenutoTimeAndDurations);
}

void FluidSequencer::addDynamicEvents(EventSequenceMap& destination, const mpe::DynamicLevelLayers& dynamics)
void FluidSequencer::addDynamicEvents(EventSequenceMap& destination, const mpe::DynamicAutomationLayers& layers)
{
for (const auto& layer : dynamics) {
for (const auto& dynamic : layer.second) {
midi::Event event(muse::midi::Event::Opcode::ControlChange, Event::MessageType::ChannelVoice10);
event.setIndex(midi::EXPRESSION_CONTROLLER);
event.setData(expressionLevel(dynamic.second));
constexpr mpe::timestamp_t STEP_INTERVAL_US = 30000;

destination[dynamic.first].emplace_back(std::move(event));
}
for (const auto& [layerIdx, curve] : layers) {
std::optional<int> lastLevel;

mpe::resampleCurve(curve, STEP_INTERVAL_US, [&](mpe::timestamp_t t, muse::real_t normalized) {
const int level = expressionLevel(mpe::dynamicLevelFromNormalized(normalized));
if (lastLevel == level) {
return;
}
lastLevel = level;

midi::Event event(Event::Opcode::ControlChange, Event::MessageType::ChannelVoice10);
event.setIndex(midi::EXPRESSION_CONTROLLER);
event.setData(level);
destination[t].emplace_back(std::move(event));
Comment on lines +121 to +134

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Route expression controls to the note channel.

These controller events do not set a channel. The note-on event at Line 161 sets channelIdx, so expression controls do not affect notes on other channels.

Map each layerIdx to its target MIDI channel when resampling the curve. Set that channel on each generated controller event. Set channelIdx on the initial expression event before the note-on event.

Also applies to: 153-157

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/audio/engine/internal/synthesizers/fluidsynth/fluidsequencer.cpp`
around lines 121 - 134, Update the expression-event generation in the layer
resampling loop to map each layerIdx to its target MIDI channel and set that
channel on every generated ControlChange event before inserting it into
destination. Also set channelIdx on the initial expression event in the
corresponding setup path before the note-on event, preserving the existing
note-channel mapping.

});
}
}

Expand All @@ -146,6 +150,13 @@ void FluidSequencer::addNoteEvent(EventSequenceMap& destination, const mpe::Note
m_lastStaff = noteEvent.arrangementCtx().staffLayerIndex;

if (arrangementCtx.hasStart()) {
if (m_useDynamicEvents) {
midi::Event expressionEvent(Event::Opcode::ControlChange, Event::MessageType::ChannelVoice10);
expressionEvent.setIndex(midi::EXPRESSION_CONTROLLER);
expressionEvent.setData(expressionLevel(noteEvent.expressionCtx().nominalDynamicLevel));
destination[arrangementCtx.actualTimestamp].emplace_back(std::move(expressionEvent));
}

midi::Event noteOn(Event::Opcode::NoteOn, Event::MessageType::ChannelVoice20);
noteOn.setChannel(channelIdx);
noteOn.setNote(noteIdx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ class FluidSequencer : public engine::AbstractEventSequencer<midi::Event>
int lastStaff() const;

private:
void updateOffStreamEvents(const mpe::PlaybackEventsMap& events, const mpe::DynamicLevelLayers& dynamics) override;
void updateMainStreamEvents(const mpe::PlaybackEventsMap& events, const mpe::DynamicLevelLayers& dynamics) override;
void updateMainStreamEvents(const mpe::PlaybackEventsMap& events, const mpe::DynamicAutomationLayers& dynamics) override;
void updateOffStreamEvents(const mpe::PlaybackEventsMap& events) override;

using SostenutoTimeAndDurations = std::map<midi::channel_t, std::vector<mpe::TimestampAndDuration> >;

void addPlaybackEvents(EventSequenceMap& destination, const mpe::PlaybackEventsMap& events);
void addDynamicEvents(EventSequenceMap& destination, const mpe::DynamicLevelLayers& dynamics);
void addDynamicEvents(EventSequenceMap& destination, const mpe::DynamicAutomationLayers& layers);
void addNoteEvent(EventSequenceMap& destination, const mpe::NoteEvent& noteEvent, SostenutoTimeAndDurations& sostenutoTimeAndDurations);
void addPedalEvent(EventSequenceMap& destination, const mpe::ArticulationMeta& meta, const midi::channel_t channelIdx);
void addControlChangeEvent(EventSequenceMap& destination, const mpe::timestamp_t timestamp, const mpe::ControllerChangeEvent& event);
Expand Down
8 changes: 4 additions & 4 deletions framework/audio/tests/rpcpacker_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,16 @@ TEST_F(Audio_RpcPackerTests, ControlParams)

TEST_F(Audio_RpcPackerTests, ControlParams_Automation)
{
mpe::AutomationPoint::Bend bend;
bend.t = 0.3;
bend.value = 0.7;
mpe::AutomationPoint::Ease ease;
ease.t = 0.3;
ease.value = 0.7;

mpe::AutomationPoint p0;
p0.inValue = mpe::AutomationPoint::ArrivalFromPrevious {};
p0.outValue = 0.2;

mpe::AutomationPoint p1;
p1.inValue = mpe::AutomationPoint::ExplicitArrival { real_t(0.4), bend };
p1.inValue = mpe::AutomationPoint::ExplicitArrival { real_t(0.4), ease };
p1.outValue = 0.9;

AutomationEnvelope envelope;
Expand Down
Loading