-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Auto-quit rework #8070
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Auto-quit rework #8070
Changes from all commits
ae8e070
eb4801b
f7e8ba7
35d8ba5
4313213
bb71541
97a241c
c396a8a
bdb7662
8f637e3
54fd25f
51033a8
f989e42
86a4399
bd5325f
f63342c
1008f59
68d79df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,284 @@ | ||
| /* | ||
| * 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 <memory_resource> | ||
|
|
||
| #include "AudioBufferView.h" | ||
| #include "ArrayVector.h" | ||
| #include "LmmsTypes.h" | ||
| #include "lmms_constants.h" | ||
| #include "lmms_export.h" | ||
|
|
||
| namespace lmms | ||
| { | ||
|
|
||
| /** | ||
| * A collection of track channels for an instrument or effect chain | ||
| * which keeps track of signal flow. | ||
| */ | ||
| class LMMS_EXPORT AudioBus | ||
| { | ||
| public: | ||
| using ChannelFlags = std::bitset<MaxTrackChannels>; | ||
|
|
||
| class LMMS_EXPORT BusData | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it's the I'll have to think it over some more. Naming stuff has been the hardest part of this PR haha.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made the realization that Though, you might prefer working with
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll take a look at
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I came up with a good abstraction for this I think in #8178.
I am transitioning
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also considering just making |
||
| { | ||
| public: | ||
| BusData(std::pmr::polymorphic_allocator<>& alloc, | ||
| ch_cnt_t channels, f_cnt_t frames, track_ch_t startingChannel); | ||
|
|
||
| BusData(const BusData&) = delete; | ||
| BusData(BusData&&) noexcept = default; | ||
| auto operator=(const BusData&) -> BusData& = delete; | ||
| auto operator=(BusData&&) noexcept -> BusData& = default; | ||
|
|
||
| auto channelBuffers() const -> const float* const* { return m_channelBuffers; } | ||
| auto channelBuffers() -> float** { return m_channelBuffers; } | ||
|
|
||
| auto channelBuffer(ch_cnt_t channel) const -> const float* | ||
| { | ||
| assert(channel < m_channels); | ||
| return m_channelBuffers[channel]; | ||
| } | ||
|
|
||
| auto channelBuffer(ch_cnt_t channel) -> float* | ||
| { | ||
| assert(channel < m_channels); | ||
| return m_channelBuffers[channel]; | ||
| } | ||
|
|
||
| auto interleavedBuffer() const -> const float* { return m_interleavedBuffer; } | ||
| auto interleavedBuffer() -> float* { return m_interleavedBuffer; } | ||
|
|
||
| auto channels() const -> ch_cnt_t { return m_channels; } | ||
|
|
||
| auto startingChannel() const -> track_ch_t { return m_startingChannel; } | ||
|
|
||
| friend class AudioBus; | ||
|
|
||
| private: | ||
| //! Large buffer that all channel buffers are sourced from | ||
| float* m_sourceBuffer = nullptr; | ||
|
|
||
| //! Provides access to individual channel buffers within the source buffer | ||
| float** m_channelBuffers = nullptr; | ||
|
|
||
| //! Interleaved scratch buffer for conversions between interleaved and planar TODO: Remove once using planar only | ||
| float* m_interleavedBuffer = nullptr; | ||
|
|
||
| //! Number of channels in `m_channelBuffers` (`MaxChannelsPerBus` maximum) - currently only 2 is used | ||
| ch_cnt_t m_channels = 0; | ||
|
|
||
| //! Maps channel #0 of this bus to its track channel # within AudioBus (for performance) | ||
| track_ch_t m_startingChannel = 0; | ||
| }; | ||
|
|
||
| AudioBus() = default; | ||
| ~AudioBus(); | ||
|
|
||
| AudioBus(const AudioBus&) = delete; | ||
| AudioBus(AudioBus&&) noexcept = default; | ||
| auto operator=(const AudioBus&) -> AudioBus& = delete; | ||
| auto operator=(AudioBus&&) noexcept -> AudioBus& = default; | ||
|
|
||
| //! Single bus with `frames` frames, `channels` channels, and all buffers allocated with `bufferResource` | ||
| explicit AudioBus(f_cnt_t frames, ch_cnt_t channels = DEFAULT_CHANNELS, | ||
| std::pmr::memory_resource* bufferResource = std::pmr::get_default_resource()); | ||
|
|
||
| auto busCount() const -> bus_cnt_t { return static_cast<bus_cnt_t>(m_busses.size()); } | ||
|
|
||
| //! @returns the buffers of the given bus | ||
| auto buffers(bus_cnt_t busIndex) const -> PlanarBufferView<const float> | ||
| { | ||
| assert(busIndex < busCount()); | ||
| const BusData& b = m_busses[busIndex]; | ||
| return {b.channelBuffers(), b.channels(), m_frames}; | ||
| } | ||
|
|
||
| //! @returns the buffers of the given bus | ||
| auto buffers(bus_cnt_t busIndex) -> PlanarBufferView<float> | ||
| { | ||
| assert(busIndex < busCount()); | ||
| BusData& b = m_busses[busIndex]; | ||
| return {b.channelBuffers(), b.channels(), m_frames}; | ||
| } | ||
|
|
||
| //! @returns planar channel buffers for the given bus | ||
| auto operator[](bus_cnt_t busIndex) const -> const float* const* | ||
| { | ||
| return m_busses[busIndex].channelBuffers(); | ||
| } | ||
|
|
||
| //! @returns planar channel buffers for the given bus | ||
| auto operator[](bus_cnt_t busIndex) -> float** | ||
| { | ||
| return m_busses[busIndex].channelBuffers(); | ||
| } | ||
|
|
||
| //! @returns sum of all bus channel counts | ||
| auto totalChannels() const -> track_ch_t { return m_totalChannels; } | ||
|
|
||
| //! @returns the frame count for each channel buffer | ||
| auto frames() const -> f_cnt_t { return m_frames; } | ||
|
|
||
| //! @returns scratch buffer for conversions between interleaved and planar TODO: Remove once using planar only | ||
| auto interleavedBuffer(bus_cnt_t busIndex) const -> InterleavedBufferView<const float, 2> | ||
| { | ||
| assert(m_busses[busIndex].channels() == 2); | ||
| return {m_busses[busIndex].interleavedBuffer(), m_frames}; | ||
| } | ||
|
|
||
| //! @returns scratch buffer for conversions between interleaved and planar TODO: Remove once using planar only | ||
| auto interleavedBuffer(bus_cnt_t busIndex) -> InterleavedBufferView<float, 2> | ||
| { | ||
| assert(m_busses[busIndex].channels() == 2); | ||
| return {m_busses[busIndex].interleavedBuffer(), m_frames}; | ||
| } | ||
sakertooth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * @brief Adds a new bus at the end of the list | ||
| * @returns the newly created bus, or nullptr upon failure | ||
| */ | ||
| auto addBus(ch_cnt_t channels) -> BusData*; | ||
|
|
||
| /** | ||
| * Track channels which are known to be quiet, AKA the silence status. | ||
| * 1 = track channel is known to be silent | ||
| * 0 = track channel is assumed to be non-silent (or, when silence tracking | ||
| * is enabled, known to be non-silent) | ||
| * | ||
| * NOTE: If any track channel buffers are used and their data modified outside of this class, | ||
| * their silence flags will be invalidated until `updateSilenceFlags()` is called. | ||
| * Therefore, calling code must be careful to always keep the silence flags up-to-date. | ||
| */ | ||
| auto silenceFlags() const -> const ChannelFlags& { return m_silenceFlags; } | ||
|
|
||
| #ifdef LMMS_TESTING | ||
| auto silenceFlags() -> ChannelFlags& { return m_silenceFlags; } | ||
| #endif | ||
|
|
||
| /** | ||
| * When silence tracking is enabled, track channels will be checked for silence whenever their data may | ||
| * have changed, so it'll always be known whether they are silent or non-silent. There is a performance cost | ||
| * to this, but it is likely worth it since this information allows many effects to be put to sleep | ||
| * when their inputs are silent ("auto-quit"). When a track channel is known to be silent, it also | ||
| * enables optimizations in buffer sanitization, buffer zeroing, and finding the absolute peak sample value. | ||
| * | ||
| * When silence tracking is disabled, track channels are not checked for silence, so a silence flag may be | ||
| * unset despite the channel being silent. Non-silence must be assumed whenever the silence status is not | ||
| * known, so the optimizations which silent buffers allow will not be possible as often. | ||
| */ | ||
| void enableSilenceTracking(bool enabled); | ||
| auto silenceTrackingEnabled() const -> bool { return m_silenceTrackingEnabled; } | ||
|
|
||
| //! 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 ChannelFlags& 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 ChannelFlags& 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 updateSilenceFlags(const ChannelFlags& channels, track_ch_t upperBound = MaxTrackChannels) -> bool; | ||
|
|
||
| //! Updates the silence status of all channels. @see updateSilenceFlags | ||
| auto updateAllSilenceFlags() -> 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 ChannelFlags& channels, track_ch_t upperBound = MaxTrackChannels); | ||
|
|
||
| //! Silences (zeroes) all channels. @see silenceChannels | ||
| void silenceAllChannels(); | ||
|
|
||
| //! @returns absolute peak sample value for the given channel | ||
| auto absPeakValue(bus_cnt_t busIndex, ch_cnt_t busChannel) const -> float; | ||
|
|
||
| private: | ||
| ArrayVector<BusData, MaxBussesPerTrack> m_busses; | ||
|
|
||
| //! Caches the sum of `m_busses[idx].channels()` - must never exceed MaxTrackChannels | ||
| track_ch_t m_totalChannels = 0; | ||
|
|
||
| const f_cnt_t m_frames = 0; | ||
|
|
||
| //! Allocator used by all buffers | ||
| std::pmr::polymorphic_allocator<> m_alloc; | ||
|
|
||
| /** | ||
| * Stores which track channels are known to be quiet, AKA the silence status. | ||
| * | ||
| * This must always be kept in sync with the buffer data when enabled - at minimum | ||
| * avoiding any false positives where a channel is marked as "silent" when it isn't. | ||
| * Any channel bits at or above `m_totalChannels` must always be marked silent. | ||
| * | ||
| * 1 = track channel is known to be silent | ||
| * 0 = track channel is assumed to be non-silent (or, when silence tracking | ||
| * is enabled, known to be non-silent) | ||
| */ | ||
| ChannelFlags m_silenceFlags; | ||
|
|
||
| bool m_silenceTrackingEnabled = false; | ||
| }; | ||
|
|
||
| } // namespace lmms | ||
|
|
||
| #endif // LMMS_AUDIO_BUS_H | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ | |
| #include <QString> | ||
| #include <QMutex> | ||
|
|
||
| #include "AudioBus.h" | ||
| #include "PlayHandle.h" | ||
|
|
||
| namespace lmms | ||
|
|
@@ -58,8 +59,6 @@ class AudioBusHandle : public ThreadableJob | |
| BoolModel* mutedModel = nullptr); | ||
| virtual ~AudioBusHandle(); | ||
|
|
||
| SampleFrame* buffer() { return m_buffer; } | ||
|
|
||
| // indicate whether JACK & Co should provide output-buffer at ext. port | ||
| bool extOutputEnabled() const { return m_extOutputEnabled; } | ||
| void setExtOutputEnabled(bool enabled); | ||
|
|
@@ -85,7 +84,7 @@ class AudioBusHandle : public ThreadableJob | |
| private: | ||
| volatile bool m_bufferUsage; | ||
|
|
||
| SampleFrame* const m_buffer; | ||
| AudioBus m_busses; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| bool m_extOutputEnabled; | ||
| mix_ch_t m_nextMixerChannel; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.