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
12 changes: 6 additions & 6 deletions include/AudioBufferView.h
Original file line number Diff line number Diff line change
Expand Up @@ -319,17 +319,17 @@ class InterleavedBufferView : public detail::BufferViewData<T, channelCount>
{
}

//! Construct from std::span<SampleFrame>
InterleavedBufferView(std::span<SampleFrame> buffer) noexcept
//! Construct from SampleFrame*
InterleavedBufferView(SampleFrame* data, f_cnt_t frames) noexcept
requires (std::is_same_v<std::remove_const_t<T>, float> && channelCount == 2)
: Base{reinterpret_cast<float*>(buffer.data()), buffer.size()}
: Base{reinterpret_cast<float*>(data), frames}
{
}

//! Construct from std::span<const SampleFrame>
InterleavedBufferView(std::span<const SampleFrame> buffer) noexcept
//! Construct from const SampleFrame*
InterleavedBufferView(const SampleFrame* data, f_cnt_t frames) noexcept
requires (std::is_same_v<T, const float> && channelCount == 2)
: Base{reinterpret_cast<const float*>(buffer.data()), buffer.size()}
: Base{reinterpret_cast<const float*>(data), frames}
{
}

Expand Down
170 changes: 170 additions & 0 deletions include/AudioBus.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* AudioBus.h
*
* Copyright (c) 2025 Dalton Messmer <messmer.dalton/at/gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/

#ifndef LMMS_AUDIO_BUS_H
#define LMMS_AUDIO_BUS_H

#include <bitset>

#include "AudioBufferView.h"
#include "LmmsTypes.h"
#include "lmms_constants.h"
#include "lmms_export.h"
#include "SampleFrame.h"

namespace lmms
{

/**
* A non-owning span of the track channels for an instrument or effect chain
* which keeps track of signal flow.
*/
class LMMS_EXPORT AudioBus
{
public:
AudioBus() = default;
AudioBus(const AudioBus&) = default;

//! `bus` is assumed to be silent
AudioBus(SampleFrame* const* bus, track_ch_t channelPairs, f_cnt_t frames);

auto trackChannelPair(track_ch_t pairIndex) const -> InterleavedBufferView<const float, 2>
{
return {m_bus[pairIndex], m_frames};
}

auto trackChannelPair(track_ch_t pairIndex) -> InterleavedBufferView<float, 2>
{
return {m_bus[pairIndex], m_frames};
}

//! @returns 2-channel interleaved buffer for the given track channel pair
auto operator[](track_ch_t pairIndex) const -> const float*
{
return reinterpret_cast<const float*>(m_bus[pairIndex]);
}

//! @returns 2-channel interleaved buffer for the given track channel pair
auto operator[](track_ch_t pairIndex) -> float*
{
return reinterpret_cast<float*>(m_bus[pairIndex]);
}

//! @returns 2D array that can be accessed like: bus()[channel pair index][sample index]
auto bus() const -> const SampleFrame* const* { return m_bus; }

//! @returns 2D array that can be accessed like: bus()[channel pair index][sample index]
auto bus() -> SampleFrame* const* { return m_bus; }

auto channels() const -> track_ch_t { return m_channelPairs * 2; }
auto channelPairs() const -> track_ch_t { return m_channelPairs; }
auto frames() const -> f_cnt_t { return m_frames; }

/**
* Track channels which are known to be quiet, AKA the silence status.
* 1 = track channel is quiet
* 0 = track channel is assumed to carry a signal (non-quiet)
*/
auto quietChannels() const -> const std::bitset<MaxTrackChannels>& { return m_quietChannels; }

#ifdef LMMS_TESTING
auto quietChannels() -> std::bitset<MaxTrackChannels>& { return m_quietChannels; }
#endif

auto silenceTrackingEnabled() const -> bool { return m_silenceTrackingEnabled; }
void enableSilenceTracking(bool enabled);

//! Mixes the silence status of the other `AudioBus` with this `AudioBus`
void mixQuietChannels(const AudioBus& other);

/**
* Determines whether a processor has input noise given
* which track channels are routed to the processor's inputs.
*
* For `usedChannels`:
* 0 = track channel is not routed to any processor inputs
* 1 = track channel is routed to at least one processor input
*
* If the processor is sleeping and has input noise, it should wake up.
* If silence tracking is disabled, all channels are assumed to have input noise.
*/
auto hasInputNoise(const std::bitset<MaxTrackChannels>& usedChannels) const -> bool;

//! Determines whether there is input noise on any channel. @see hasInputNoise
auto hasAnyInputNoise() const -> bool;

/**
* @brief Sanitizes specified track channels of any Inf/NaN values if "nanhandler" setting is enabled
*
* @param channels track channels to sanitize; 1 = selected, 0 = skip
* @param upperBound any track channel indexes at or above this are skipped
*/
void sanitize(const std::bitset<MaxTrackChannels>& channels, track_ch_t upperBound = MaxTrackChannels);

//! Sanitizes all channels. @see sanitize
void sanitizeAll();

/**
* @brief Updates the silence status of the given channels, up to the upperBound index.
*
* @param channels track channels to update; 1 = selected, 0 = skip
* @param upperBound any track channel indexes at or above this are skipped
* @returns true if all updated channels were silent
*/
auto update(const std::bitset<MaxTrackChannels>& channels, track_ch_t upperBound = MaxTrackChannels) -> bool;

//! Updates the silence status of all channels. @see update
auto updateAll() -> bool;

/**
* @brief Silences (zeroes) the given channels
*
* @param channels track channels to silence; 1 = selected, 0 = skip
* @param upperBound any track channel indexes at or above this are skipped
*/
void silenceChannels(const std::bitset<MaxTrackChannels>& channels, track_ch_t upperBound = MaxTrackChannels);

//! Silences (zeroes) all channels. @see silenceChannels
void silenceAllChannels();

private:
SampleFrame* const* m_bus = nullptr; //!< [channel pair index][sample index]
const track_ch_t m_channelPairs = 0;
const f_cnt_t m_frames = 0;

/**
* Stores which channels are known to be quiet.
*
* It must always be kept in sync with the buffer data when enabled - at minimum
* avoiding any false positives where a channel is marked as "quiet" when it isn't.
* Any channel bits at or above `channels()` must always be marked quiet.
*/
std::bitset<MaxTrackChannels> m_quietChannels;

bool m_silenceTrackingEnabled = false;
};

} // namespace lmms

#endif // LMMS_AUDIO_BUS_H
2 changes: 2 additions & 0 deletions include/AudioBusHandle.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <QString>
#include <QMutex>

#include "AudioBus.h"
#include "PlayHandle.h"

namespace lmms
Expand Down Expand Up @@ -86,6 +87,7 @@ class AudioBusHandle : public ThreadableJob
volatile bool m_bufferUsage;

SampleFrame* const m_buffer;
AudioBus m_bus;

bool m_extOutputEnabled;
mix_ch_t m_nextMixerChannel;
Expand Down
51 changes: 29 additions & 22 deletions include/Effect.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
namespace lmms
{

class AudioBus;
class EffectChain;
class EffectControls;

Expand Down Expand Up @@ -66,7 +67,7 @@ class LMMS_EXPORT Effect : public Plugin
}

//! Returns true if audio was processed and should continue being processed
bool processAudioBuffer(SampleFrame* buf, const fpp_t frames);
bool processAudioBuffer(AudioBus& inOut);

inline bool isOkay() const
{
Expand All @@ -78,22 +79,10 @@ class LMMS_EXPORT Effect : public Plugin
m_okay = _state;
}


inline bool isRunning() const
//! "Awake" means the effect has not been put to sleep by auto-quit
bool isAwake() const
{
return m_running;
}

void startRunning()
{
m_quietBufferCount = 0;
m_running = true;
}

void stopRunning()
{
m_quietBufferCount = 0;
m_running = false;
return m_awake;
}

inline bool isEnabled() const
Expand Down Expand Up @@ -126,7 +115,13 @@ class LMMS_EXPORT Effect : public Plugin
{
m_noRun = _state;
}


//! "Running" means the effect will be processing audio
bool isRunning() const
{
return isEnabled() && isAwake() && isOkay() && !dontRun();
}

inline TempoSyncKnobModel* autoQuitModel()
{
return &m_autoQuitModel;
Expand Down Expand Up @@ -163,19 +158,31 @@ class LMMS_EXPORT Effect : public Plugin
};

/**
* The main audio processing method that runs when plugin is not asleep
* The main audio processing method that runs when plugin is awake and running
*/
virtual ProcessStatus processImpl(SampleFrame* buf, const fpp_t frames) = 0;

/**
* Optional method that runs when plugin is sleeping (not enabled,
* not running, not in the Okay state, or in the Don't Run state)
* Optional method that runs instead of `processImpl` when an effect
* is awake but not running.
*/
virtual void processBypassedImpl() {}


gui::PluginView* instantiateView( QWidget * ) override;

void startRunning()
{
m_quietBufferCount = 0;
m_awake = true;
}

void stopRunning()
{
m_quietBufferCount = 0;
m_awake = false;
}

// some effects might not be capable of higher sample-rates so they can
// sample it down before processing and back after processing
inline void sampleDown( const SampleFrame* _src_buf,
Expand Down Expand Up @@ -208,7 +215,7 @@ class LMMS_EXPORT Effect : public Plugin
* after "decay" ms of the output buffer remaining below the silence threshold, the effect is
* turned off and won't be processed again until it receives new audio input.
*/
void handleAutoQuit(std::span<const SampleFrame> output);
void handleAutoQuit(bool silentOutput);


EffectChain * m_parent;
Expand All @@ -219,7 +226,7 @@ class LMMS_EXPORT Effect : public Plugin

bool m_okay;
bool m_noRun;
bool m_running;
bool m_awake;

//! The number of consecutive periods where output buffers remain below the silence threshold
f_cnt_t m_quietBufferCount = 0;
Expand Down
5 changes: 2 additions & 3 deletions include/EffectChain.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
namespace lmms
{

class AudioBus;
class Effect;
class SampleFrame;

namespace gui
{
Expand Down Expand Up @@ -63,8 +63,7 @@ class LMMS_EXPORT EffectChain : public Model, public SerializingObject
void removeEffect( Effect * _effect );
void moveDown( Effect * _effect );
void moveUp( Effect * _effect );
bool processAudioBuffer( SampleFrame* _buf, const fpp_t _frames, bool hasInputNoise );
void startRunning();
bool processAudioBuffer(AudioBus& bus);

void clear();

Expand Down
Loading