-
Notifications
You must be signed in to change notification settings - Fork 19
removed AudioQueue and added direct IO callback #26
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
Open
konradglas
wants to merge
6
commits into
musescore:main
Choose a base branch
from
konradglas:direct-core-audio-callback
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
e902490
removed AudioQueue and added direct IO callback
konradglas 9580191
added signpost utility for xcode instruments
konradglas e70ebc1
added thirdparty utilities
konradglas 583bdab
ported realtime thread pool
konradglas 6f5b0e3
add osx workgroups and global task scheduler
konradglas 34e3b80
consumer thread sleeps until producer has added first item
konradglas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| /* | ||
| * SPDX-License-Identifier: GPL-3.0-only | ||
| * MuseScore-CLA-applies | ||
| * | ||
| * MuseScore | ||
| * Music Composition & Notation | ||
| * | ||
| * Copyright (C) 2026 MuseScore Limited and others | ||
| * | ||
| * This program is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU General Public License version 3 as | ||
| * published by the Free Software Foundation. | ||
| * | ||
| * 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. If not, see <https://www.gnu.org/licenses/>. | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include "audioworkgroup.h" | ||
| #include "realtimethreadpool.h" | ||
| #include "iaudiodriver.h" | ||
| #include "iaudiotaskscheduler.h" | ||
| #include "audiosanitizer.h" | ||
| #include "global/async/asyncable.h" | ||
| #include "global/log.h" | ||
| #include <mutex> | ||
|
|
||
| namespace muse::audio { | ||
| class AudioTaskScheduler : public IAudioTaskScheduler, public muse::async::Asyncable | ||
| { | ||
| constexpr static const char* threadpoolName = "audio_realtime_thread"; | ||
| public: | ||
| void submitRealtimeTasksAndWait(const std::vector<Task>& tasks) override | ||
| { | ||
| std::lock_guard lock(m_threadPoolMutex); | ||
| for (const auto& task : tasks) { | ||
| IF_ASSERT_FAILED(m_threadPool->enqueue(task)) { | ||
| task(); | ||
| } | ||
| } | ||
| m_threadPool->participateAndWait(); | ||
| } | ||
|
|
||
| void setAudioDriver(const IAudioDriverPtr& audioDriver) | ||
| { | ||
| if (!audioDriver) { | ||
| return; | ||
| } | ||
| setWorkgroup(audioDriver->getAudioWorkGroup()); | ||
| audioDriver->currentWorkgroupChanged().onNotify( | ||
| this, [this, audioDriverWeak = std::weak_ptr<IAudioDriver>(audioDriver)]() { | ||
| if (auto audioDriver = audioDriverWeak.lock()) { | ||
| setWorkgroup(audioDriver->getAudioWorkGroup()); | ||
| } | ||
| }); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| private: | ||
|
|
||
| void setWorkgroup(const AudioWorkGroup& workGroup) | ||
| { | ||
| std::lock_guard lock(m_threadPoolMutex); | ||
| ensureThreadPoolSize(workGroup); | ||
| m_threadPool->setAudioWorkgroup(workGroup); | ||
| } | ||
|
|
||
| int getIdealThreadCount(AudioWorkGroup workGroup) const | ||
| { | ||
| int bestThreadHint = std::thread::hardware_concurrency(); | ||
| if (workGroup.getProvider() != nullptr) { | ||
| bestThreadHint = workGroup.getMaxParallelThreadCount(); | ||
| } | ||
| constexpr int hardwareToRealtimeRatio = 2; // This is a heuristic value. The optimal value may vary depending on the workload and system. | ||
| return bestThreadHint > 0 ? static_cast<int>(bestThreadHint / hardwareToRealtimeRatio) : 1; | ||
| } | ||
|
konradglas marked this conversation as resolved.
|
||
|
|
||
| void ensureThreadPoolSize(const AudioWorkGroup& currentWorkGroup) | ||
| { | ||
| auto idealWorkerCount = getIdealThreadCount(currentWorkGroup); | ||
| std::lock_guard lock(m_threadPoolMutex); | ||
| if (m_threadPool->getNumberOfWorkers() != idealWorkerCount) { | ||
| m_threadPool = std::make_unique<RealtimeThreadPool>(threadpoolName, idealWorkerCount); | ||
| AudioSanitizer::setMixerThreads(m_threadPool->threadIdSet()); | ||
| } | ||
| } | ||
|
|
||
| std::unique_ptr<RealtimeThreadPool> m_threadPool{ std::make_unique<RealtimeThreadPool>(threadpoolName, getIdealThreadCount({})) }; | ||
|
|
||
| std::recursive_mutex m_threadPoolMutex; | ||
| }; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,244 @@ | ||
| /* | ||
| * SPDX-License-Identifier: GPL-3.0-only | ||
| * MuseScore-CLA-applies | ||
| * | ||
| * MuseScore | ||
| * Music Composition & Notation | ||
| * | ||
| * Copyright (C) 2026 MuseScore Limited and others | ||
| * | ||
| * This program is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU General Public License version 3 as | ||
| * published by the Free Software Foundation. | ||
| * | ||
| * 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. If not, see <https://www.gnu.org/licenses/>. | ||
| */ | ||
|
|
||
| #include "audioworkgroup.h" | ||
|
|
||
| #include <memory> | ||
| #include <utility> | ||
|
|
||
| #ifdef __APPLE__ | ||
| #include <os/object.h> | ||
| #include <os/workgroup.h> | ||
| #else | ||
| #include <thread> | ||
| #endif | ||
|
|
||
| namespace muse::audio { | ||
| #ifdef __APPLE__ | ||
| class AudioWorkgroupTokenProvider | ||
| { | ||
| public: | ||
| AudioWorkgroupTokenProvider(os_workgroup_t workgroup) | ||
| : m_workgroup(workgroup) | ||
| { | ||
| if (!m_workgroup) { | ||
| return; | ||
| } | ||
|
|
||
| if (__builtin_available(macOS 11.0, *)) { | ||
| os_retain(m_workgroup); | ||
| auto status = os_workgroup_join(m_workgroup, &m_joinToken); | ||
| if (status == 0) { | ||
| return; | ||
| } | ||
|
|
||
| os_release(m_workgroup); | ||
| m_workgroup = nullptr; | ||
| m_joinToken = {}; | ||
| return; | ||
| } | ||
|
|
||
| m_workgroup = nullptr; | ||
| } | ||
|
|
||
| bool isAttachedTo(os_workgroup_t wg) const { return m_workgroup == wg; } | ||
| bool isValid() const { return m_workgroup != nullptr; } | ||
|
|
||
| AudioWorkgroupTokenProvider(const AudioWorkgroupTokenProvider& other) = delete; | ||
|
|
||
| AudioWorkgroupTokenProvider& operator=(const AudioWorkgroupTokenProvider&) = delete; | ||
|
|
||
| AudioWorkgroupTokenProvider(AudioWorkgroupTokenProvider&& other) noexcept | ||
| : m_workgroup(other.m_workgroup), m_joinToken(other.m_joinToken) | ||
| { | ||
| other.m_workgroup = nullptr; | ||
| other.m_joinToken = {}; | ||
| } | ||
|
|
||
| ~AudioWorkgroupTokenProvider() | ||
| { | ||
| leave(); | ||
| } | ||
|
|
||
| private: | ||
| void leave() noexcept | ||
| { | ||
| if (!m_workgroup) { | ||
| return; | ||
| } | ||
|
|
||
| if (__builtin_available(macOS 11.0, *)) { | ||
| os_workgroup_leave(m_workgroup, &m_joinToken); | ||
| } | ||
| os_release(m_workgroup); | ||
| m_workgroup = nullptr; | ||
| m_joinToken = {}; | ||
| } | ||
|
|
||
| os_workgroup_t m_workgroup; | ||
| os_workgroup_join_token_s m_joinToken; | ||
| }; | ||
|
|
||
| class AudioWorkgroupProvider | ||
| { | ||
| public: | ||
| explicit AudioWorkgroupProvider(os_workgroup_t wg) | ||
| : m_workgroup(wg) | ||
| { | ||
| os_retain(m_workgroup); | ||
| } | ||
|
|
||
| ~AudioWorkgroupProvider() | ||
| { | ||
| if (m_workgroup) { | ||
| os_release(m_workgroup); | ||
| } | ||
| } | ||
|
|
||
| AudioWorkgroupProvider(const AudioWorkgroupProvider& other) | ||
| : m_workgroup(other.m_workgroup) | ||
| { | ||
| os_retain(m_workgroup); | ||
| } | ||
|
|
||
| AudioWorkgroupProvider& operator=(const AudioWorkgroupProvider& other) | ||
| { | ||
| if (this != &other) { | ||
| os_retain(other.m_workgroup); | ||
| if (m_workgroup) { | ||
| os_release(m_workgroup); | ||
| } | ||
| m_workgroup = other.m_workgroup; | ||
| } | ||
| return *this; | ||
| } | ||
|
|
||
| AudioWorkgroupProvider(AudioWorkgroupProvider&& other) noexcept | ||
| : m_workgroup(other.m_workgroup) | ||
| { | ||
| other.m_workgroup = nullptr; | ||
| } | ||
|
|
||
| bool join(AudioWorkgroupToken& tokenProvider) const | ||
| { | ||
| if (auto existingProvider = AudioWorkGroup::providerFor(tokenProvider); | ||
| existingProvider != nullptr && existingProvider->isAttachedTo(m_workgroup)) { | ||
| return true; | ||
| } | ||
| AudioWorkGroup::resetProviderFor(tokenProvider); | ||
| AudioWorkgroupTokenProvider provider(m_workgroup); | ||
| if (!provider.isValid()) { | ||
| return false; | ||
| } | ||
|
|
||
| AudioWorkGroup::setProviderFor(tokenProvider, [provider = std::move(provider)]() { | ||
| return &provider; | ||
| }); | ||
| return true; | ||
| } | ||
|
|
||
| size_t getMaxParallelThreadCount() const | ||
| { | ||
| if (__builtin_available(macOS 11.0, *)) { | ||
| return (size_t)os_workgroup_max_parallel_threads(m_workgroup, nullptr); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| private: | ||
|
|
||
| os_workgroup_t m_workgroup; | ||
| }; | ||
|
|
||
| bool AudioWorkGroup::join(AudioWorkgroupToken& token) | ||
| { | ||
| auto provider = getProvider(); | ||
| if (!provider) { | ||
| return false; | ||
| } | ||
| return provider->join(token); | ||
| } | ||
|
|
||
| AudioWorkGroup makeAudioWorkgroup(void* opaqueHandle) | ||
| { | ||
| if (opaqueHandle == nullptr) { | ||
| return {}; | ||
| } | ||
|
|
||
| os_workgroup_t handle = reinterpret_cast<os_workgroup_t>(opaqueHandle); | ||
|
|
||
| return AudioWorkGroup { std::make_unique<AudioWorkgroupProvider>(handle) }; | ||
| } | ||
|
|
||
| size_t AudioWorkGroup::getMaxParallelThreadCount() const | ||
| { | ||
| if (auto provider = getProvider(); provider) { | ||
| return provider->getMaxParallelThreadCount(); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| #else | ||
|
|
||
| class AudioWorkgroupProvider | ||
| { | ||
| }; | ||
|
|
||
| size_t AudioWorkGroup::getMaxParallelThreadCount() const | ||
| { | ||
| return std::thread::hardware_concurrency(); | ||
| } | ||
|
|
||
| bool AudioWorkGroup::join(AudioWorkgroupToken&) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| AudioWorkGroup makeAudioWorkgroup(void*) | ||
| { | ||
| return {}; | ||
| } | ||
|
|
||
| #endif | ||
|
|
||
| AudioWorkGroup::AudioWorkGroup() = default; | ||
|
|
||
| AudioWorkGroup::AudioWorkGroup(std::unique_ptr<AudioWorkgroupProvider> provider) | ||
| : m_provider(std::move(provider)) {} | ||
|
|
||
| AudioWorkGroup::~AudioWorkGroup() = default; | ||
|
|
||
| AudioWorkGroup::AudioWorkGroup(const AudioWorkGroup& other) | ||
| : m_provider(other.m_provider ? std::make_unique<AudioWorkgroupProvider>(*other.m_provider) : nullptr) {} | ||
|
|
||
| AudioWorkGroup::AudioWorkGroup(AudioWorkGroup&& other) noexcept = default; | ||
|
|
||
| AudioWorkGroup& AudioWorkGroup::operator=(const AudioWorkGroup& other) | ||
| { | ||
| if (this != &other) { | ||
| m_provider = other.m_provider ? std::make_unique<AudioWorkgroupProvider>(*other.m_provider) : nullptr; | ||
| } | ||
| return *this; | ||
| } | ||
|
|
||
| AudioWorkGroup& AudioWorkGroup::operator=(AudioWorkGroup&& other) noexcept = default; | ||
| } // namespace muse::audio |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.