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
5 changes: 4 additions & 1 deletion framework/audio/driver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,14 @@ elseif(OS_IS_MAC)
platform/osx/osxaudiodriver.h
platform/osx/osxdirectaudiodriver.mm
platform/osx/osxdirectaudiodriver.h
platform/osx/osxidlesleeppolicy.mm
platform/osx/osxidlesleeppolicy.h
)

set_source_files_properties(
platform/osx/osxaudiodriver.mm
platform/osx/osxdirectaudiodriver.mm
platform/osx/osxidlesleeppolicy.mm
PROPERTIES
SKIP_UNITY_BUILD_INCLUSION ON
SKIP_PRECOMPILE_HEADERS ON
Expand Down
3 changes: 3 additions & 0 deletions framework/audio/driver/platform/osx/osxaudiodriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class OSXAudioDriver : public IAudioDriver
void close() override;
bool isOpened() const override;

void setLivePlaybackOngoing(bool ongoing) override;

const Spec& activeSpec() const override;
async::Channel<Spec> activeSpecChanged() const override;

Expand Down Expand Up @@ -79,6 +81,7 @@ class OSXAudioDriver : public IAudioDriver
std::map<unsigned int, std::string> m_outputDevices = {}, m_inputDevices = {};
mutable std::mutex m_devicesMutex;
async::Notification m_availableOutputDevicesChanged;
bool m_livePlaybackOngoing = false;
};
}
#endif // MUSE_AUDIO_OSXAUDIODRIVER_H
12 changes: 12 additions & 0 deletions framework/audio/driver/platform/osx/osxaudiodriver.mm
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

#include <AudioToolbox/AudioToolbox.h>

#include "osxidlesleeppolicy.h"

#include "translation.h"
#include "log.h"

Expand Down Expand Up @@ -165,6 +167,10 @@ void clear()
return false;
}

//! NOTE From now on the device is running, so make sure the system knows whether
//! it is allowed to go to sleep while that is the case
OSXIdleSleepPolicy::setPreventIdleSleep(m_livePlaybackOngoing);

m_activeSpecChanged.send(m_data->format);

LOGI() << "Connected to " << m_data->format.deviceId
Expand Down Expand Up @@ -193,6 +199,12 @@ void clear()
return m_data->audioQueue != nullptr;
}

void OSXAudioDriver::setLivePlaybackOngoing(bool ongoing)
{
m_livePlaybackOngoing = ongoing;
OSXIdleSleepPolicy::setPreventIdleSleep(ongoing);
}

const OSXAudioDriver::Spec& OSXAudioDriver::activeSpec() const
{
return m_data->format;
Expand Down
3 changes: 3 additions & 0 deletions framework/audio/driver/platform/osx/osxdirectaudiodriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class OSXDirectAudioDriver : public IAudioDriver
void close() override;
bool isOpened() const override;

void setLivePlaybackOngoing(bool ongoing) override;

const Spec& activeSpec() const override;
async::Channel<Spec> activeSpecChanged() const override;

Expand Down Expand Up @@ -88,6 +90,7 @@ class OSXDirectAudioDriver : public IAudioDriver

AudioWorkGroup m_audioWorkGroup;
bool m_deviceMapListenerRegistered = false;
bool m_livePlaybackOngoing = false;
};
}
#endif // MUSE_AUDIO_OSXDIRECTAUDIODRIVER_H
11 changes: 11 additions & 0 deletions framework/audio/driver/platform/osx/osxdirectaudiodriver.mm
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

#include "common/audiotypes.h"
#include "common/audioworkgroup.h"
#include "osxidlesleeppolicy.h"
#include "translation.h"
#include "log.h"

Expand Down Expand Up @@ -668,6 +669,10 @@ AudioWorkGroup createAudioWorkgroup(OSXAudioDeviceID deviceId)
return false;
}

//! NOTE From now on the device is running, so make sure the system knows whether
//! it is allowed to go to sleep while that is the case
OSXIdleSleepPolicy::setPreventIdleSleep(m_livePlaybackOngoing);

m_audioWorkGroup = createAudioWorkgroup(*deviceId);
m_currentWorkgroupChanged.notify();

Expand Down Expand Up @@ -717,6 +722,12 @@ AudioWorkGroup createAudioWorkgroup(OSXAudioDeviceID deviceId)
return m_data->procId != nullptr;
}

void OSXDirectAudioDriver::setLivePlaybackOngoing(bool ongoing)
{
m_livePlaybackOngoing = ongoing;
OSXIdleSleepPolicy::setPreventIdleSleep(ongoing);
}

const OSXDirectAudioDriver::Spec& OSXDirectAudioDriver::activeSpec() const
{
return m_data->format;
Expand Down
38 changes: 38 additions & 0 deletions framework/audio/driver/platform/osx/osxidlesleeppolicy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-Studio-CLA-applies
*
* MuseScore Studio
* Music Composition & Notation
*
* Copyright (C) 2026 MuseScore Limited
*
* 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

namespace muse::audio {
//! NOTE macOS prevents the system from going to idle sleep for as long as a process has
//! audio IO running, no matter whether that IO is audible. Since we keep the audio device
//! open for the whole session, that would keep the Mac awake permanently, also when idle.
//! So we tell CoreAudio that idle sleep is fine, and only opt out of it while something
//! is actually being played back.
//!
//! The policy applies to the process as a whole, so it is shared by all drivers.
class OSXIdleSleepPolicy
{
public:
static void setPreventIdleSleep(bool prevent);
};
}
92 changes: 92 additions & 0 deletions framework/audio/driver/platform/osx/osxidlesleeppolicy.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-Studio-CLA-applies
*
* MuseScore Studio
* Music Composition & Notation
*
* Copyright (C) 2026 MuseScore Limited
*
* 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 "osxidlesleeppolicy.h"

#include <mutex>

#include <CoreAudio/AudioHardware.h>

#include "log.h"

using namespace muse::audio;

//! NOTE The policy is set from the thread that starts and stops playback, but reapplied
//! from a CoreAudio owned thread, so reading it and applying it has to be serialized
static std::mutex s_mutex;
static bool s_preventIdleSleep = false;

static void applyPolicy()
{
std::lock_guard lock(s_mutex);

//! NOTE 1 means: allow the CPU to idle sleep even if there is audio IO in progress
UInt32 sleepingIsAllowed = s_preventIdleSleep ? 0 : 1;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

AudioObjectPropertyAddress address = {
.mSelector = kAudioHardwarePropertySleepingIsAllowed,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMain
};

OSStatus result = AudioObjectSetPropertyData(kAudioObjectSystemObject, &address, 0, nullptr,
sizeof(sleepingIsAllowed), &sleepingIsAllowed);
if (result != noErr) {
LOGE() << "Failed to set idle sleep policy, err: " << result;
}
}

static OSStatus onServiceRestarted(AudioObjectID, UInt32, const AudioObjectPropertyAddress*, void*)
{
//! NOTE The policy belongs to our HAL client, which is recreated when coreaudiod restarts
applyPolicy();
return noErr;
}

static void initServiceRestartListener()
{
static std::once_flag onceFlag;
std::call_once(onceFlag, []() {
AudioObjectPropertyAddress address = {
.mSelector = kAudioHardwarePropertyServiceRestarted,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMain
};

OSStatus result = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &address, &onServiceRestarted, nullptr);
if (result != noErr) {
LOGE() << "Failed to add service restart listener, err: " << result;
}
});
}

void OSXIdleSleepPolicy::setPreventIdleSleep(bool prevent)
{
initServiceRestartListener();

{
std::lock_guard lock(s_mutex);
s_preventIdleSleep = prevent;
}

applyPolicy();
}
6 changes: 6 additions & 0 deletions framework/audio/iaudiodriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ class IAudioDriver
virtual void close() = 0;
virtual bool isOpened() const = 0;

//! NOTE Tells the driver whether audio is currently being played back in real time.
//! The driver is kept open for the whole session and simply receives silence when
//! nothing is playing, so it cannot tell the difference on its own.
//! Drivers may use this to adjust platform specific behaviour; ignored by default.
virtual void setLivePlaybackOngoing(bool ongoing) { (void)ongoing; }

virtual const Spec& activeSpec() const = 0;
virtual async::Channel<Spec> activeSpecChanged() const = 0;

Expand Down
3 changes: 3 additions & 0 deletions framework/audio/iaudiodrivercontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ class IAudioDriverController : MODULE_GLOBAL_INTERFACE
virtual void close() = 0;
virtual bool isOpened() const = 0;

//! NOTE See IAudioDriver::setLivePlaybackOngoing
virtual void setLivePlaybackOngoing(bool ongoing) = 0;

virtual const IAudioDriver::Spec& activeSpec() const = 0;
virtual async::Channel<IAudioDriver::Spec> activeSpecChanged() const = 0;

Expand Down
16 changes: 16 additions & 0 deletions framework/audio/main/internal/audiodrivercontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ void AudioDriverController::setNewDriver(IAudioDriverPtr newDriver)
m_audioDriver = newDriver;

if (m_audioDriver) {
//! NOTE The new driver doesn't know yet what is going on
m_audioDriver->setLivePlaybackOngoing(m_livePlaybackOngoing);

// subscribe
m_audioDriver->availableOutputDevicesChanged().onNotify(this, [this]() {
async::Async::call(this, [this]() {
Expand Down Expand Up @@ -317,6 +320,19 @@ bool AudioDriverController::isOpened() const
return m_audioDriver->isOpened();
}

void AudioDriverController::setLivePlaybackOngoing(bool ongoing)
{
if (m_livePlaybackOngoing == ongoing) {
return;
}

m_livePlaybackOngoing = ongoing;

if (m_audioDriver) {
m_audioDriver->setLivePlaybackOngoing(ongoing);
}
}

const IAudioDriver::Spec& AudioDriverController::activeSpec() const
{
IF_ASSERT_FAILED(m_audioDriver) {
Expand Down
3 changes: 3 additions & 0 deletions framework/audio/main/internal/audiodrivercontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ class AudioDriverController : public IAudioDriverController, public async::Async
void close() override;
bool isOpened() const override;

void setLivePlaybackOngoing(bool ongoing) override;

const IAudioDriver::Spec& activeSpec() const override;
async::Channel<IAudioDriver::Spec> activeSpecChanged() const override;

Expand Down Expand Up @@ -91,5 +93,6 @@ class AudioDriverController : public IAudioDriverController, public async::Async
async::Notification m_outputDeviceSampleRateChanged;

bool m_retryOpenDevice = false;
bool m_livePlaybackOngoing = false;
};
}
38 changes: 38 additions & 0 deletions framework/audio/main/internal/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
*/
#include "player.h"

#include <set>

#include "audio/common/rpc/rpcpacker.h"
#include "audio/common/audiosanitizer.h"

Expand All @@ -31,11 +33,37 @@ using namespace muse::async;
using namespace muse::audio;
using namespace muse::audio::rpc;

//! NOTE Playback is contextual (each window has its own), and every context can hand out
//! more than one player, so live playback is ongoing as long as any of them is running.
static std::set<const Player*>& runningPlayers()
{
static std::set<const Player*> s_runningPlayers;
return s_runningPlayers;
}

Player::~Player()
{
if (runningPlayers().erase(this) != 0) {
updateLivePlaybackOngoing();
}
}

rpc::CtxId Player::ctxId() const
{
return rpc::ctxId(iocContext());
}

void Player::updateLivePlaybackOngoing()
{
ONLY_AUDIO_MAIN_THREAD;

//! NOTE Can already be gone on shutdown
const std::shared_ptr<IAudioDriverController>& controller = audioDriverController.get();
if (controller) {
controller->setLivePlaybackOngoing(!runningPlayers().empty());
}
}

void Player::init()
{
ONLY_AUDIO_MAIN_THREAD;
Expand All @@ -45,6 +73,16 @@ void Player::init()
{
m_playbackStatusChanged.onReceive(this, [this](PlaybackStatus st) {
m_playbackStatus = st;

//! NOTE Only while running is audio actually being produced;
//! when paused or stopped there is nothing to keep the system awake for
if (m_playbackStatus == PlaybackStatus::Running) {
runningPlayers().insert(this);
} else {
runningPlayers().erase(this);
}

updateLivePlaybackOngoing();
});

Msg msg = rpc::make_request(ctxId(), MsgCode::GetPlaybackStatus);
Expand Down
Loading
Loading