Compare commits

...

12 Commits

Author SHA1 Message Date
Curve
6512fd0186
refactor(sound): move sound to own header, ensure thread safety [skip ci] 2021-08-11 20:20:16 +02:00
Curve
8d9014f459
refactor(PlayingSound): Only calculate length-/readInMs when required, fix typo, adjust to upcoming changes 2021-08-11 19:54:25 +02:00
Curve
462dd3a4d1
feat(hotkeys): improve support for midi devices
refactor(hotkeys): shared_ptr -> unique_ptr, adjust to upcoming structural changes
2021-08-11 19:53:05 +02:00
Curve
a50de90fe9
fix(IconFetcher): compatibility issues 2021-08-11 19:47:06 +02:00
Curve
2fb8115d19
refactor(queue): remove superflous include 2021-08-11 19:46:27 +02:00
Curve
cdd8d9eabf
fix(youtube-dl): Typo & Compatibility with older compilers 2021-08-11 19:46:08 +02:00
Curve
17dd691791
fix: compatibility with catch2 & typo 2021-08-11 19:45:15 +02:00
Curve
e714f74e65
refactor: make use of 0.3.26 pipewire enhancements
Greatly reduces code size
2021-07-01 16:51:59 +02:00
Curve
0aa259455e
refactor: remove gAudio
chore(deps): update lockpp, webviewpp
refactor: UI now holds all playingSounds
refactor: PlayingSound now provides methods to pause/resume/setVolume ...
refactor: make PlayingSound manage mutexes instead of gAudio
2021-06-25 19:50:21 +02:00
Curve
d79a20063f
feat: reload problematic on destroy 2021-06-19 20:54:27 +02:00
Curve
74e01b772c
refactor: destroy pulse-instance if pipewire is detected 2021-06-18 22:06:55 +02:00
Curve
a7c0a17fe1
feat(pulseaudio): implement changes proposed by @D3SOX
This change will unload all problematic PulseAudio modules automatically and reload them when soundux exits
2021-06-18 21:55:03 +02:00
32 changed files with 989 additions and 1355 deletions

@ -1 +1 @@
Subproject commit 9b901bad31f9809f4bfaefc8795589db3ce46250
Subproject commit 99b77d8909b20cedba452acd18f3e5e0ecc6daa8

View File

@ -1,5 +1,4 @@
#pragma once
#include <helper/audio/audio.hpp>
#if defined(__linux__)
#include <helper/audio/linux/backend.hpp>
#elif defined(_WIN32)
@ -14,16 +13,15 @@
#include <helper/icons/icons.hpp>
#include <helper/queue/queue.hpp>
#include <helper/ytdl/youtube-dl.hpp>
#include <lock.hpp>
#include <memory>
#include <ui/ui.hpp>
#include <var_guard.hpp>
namespace Soundux
{
namespace Globals
{
inline Objects::Data gData;
inline Objects::Audio gAudio;
#if defined(__linux__)
inline std::shared_ptr<Objects::IconFetcher> gIcons;
inline std::shared_ptr<Objects::AudioBackend> gAudioBackend;
@ -40,7 +38,7 @@ namespace Soundux
inline std::shared_ptr<Instance::Guard> gGuard;
/* Allows for fast & easy sound access, is populated on start up */
inline sxl::var_guard<std::map<std::uint32_t, std::reference_wrapper<Objects::Sound>>> gSounds;
inline sxl::var_guard<std::map<std::uint32_t, std::reference_wrapper<Objects::Sound>>> gFavorites;
inline sxl::lock<std::map<std::uint32_t, std::reference_wrapper<Objects::Sound>>> gSounds;
inline sxl::lock<std::map<std::uint32_t, std::reference_wrapper<Objects::Sound>>> gFavorites;
} // namespace Globals
} // namespace Soundux

View File

@ -23,13 +23,13 @@ namespace Soundux
} // namespace traits
namespace Objects
{
std::shared_ptr<Hotkeys> Hotkeys::createInstance()
std::unique_ptr<Hotkeys> Hotkeys::createInstance()
{
std::shared_ptr<Hotkeys> rtn;
std::unique_ptr<Hotkeys> rtn;
#if defined(__linux__)
rtn = std::shared_ptr<X11>(new X11()); // NOLINT
rtn = std::unique_ptr<X11>(new X11()); // NOLINT
#elif defined(_WIN32)
rtn = std::shared_ptr<WindowsHotkeys>(new WindowsHotkeys()); // NOLINT
rtn = std::unique_ptr<WindowsHotkeys>(new WindowsHotkeys()); // NOLINT
#endif
rtn->setup();
return rtn;
@ -58,14 +58,14 @@ namespace Soundux
key.byte2 = byte2;
key.type = Enums::KeyType::Midi;
if (byte0 == 144)
{
onKeyDown(key);
}
else if (byte0 == 128)
if (byte0 == 128 || (byte0 == 144 && byte2 == 0))
{
onKeyUp(key);
}
else if (byte0 == 144)
{
onKeyDown(key);
}
else if (byte0 == 176)
{
if (shouldNotifyKnob)
@ -142,40 +142,27 @@ namespace Soundux
}
return false;
}
template <typename T> std::optional<Sound> getBestMatch(const T &list, const std::vector<Key> &pressedKeys)
template <typename T> std::shared_ptr<Sound> getBestMatch(const T &list, const std::vector<Key> &pressedKeys)
{
std::optional<Sound> rtn;
std::shared_ptr<Sound> rtn;
for (const auto &_sound : list)
for (const auto &sound : list)
{
const auto &sound = [&]() constexpr
{
if constexpr (traits::is_pair<std::decay_t<decltype(_sound)>>::value)
{
return _sound.second.get();
}
else
{
return _sound;
}
}
();
if (sound.hotkeys.empty())
if (sound->getHotkeys().empty())
continue;
if (pressedKeys == sound.hotkeys)
if (pressedKeys == sound->getHotkeys())
{
rtn = sound;
break;
}
if (rtn && rtn->hotkeys.size() > sound.hotkeys.size())
if (rtn && rtn->getHotkeys().size() > sound->getHotkeys().size())
{
continue;
}
if (isCloseMatch(pressedKeys, sound.hotkeys))
if (isCloseMatch(pressedKeys, sound->getHotkeys()))
{
rtn = sound;
}
@ -200,11 +187,10 @@ namespace Soundux
return;
}
std::optional<Sound> bestMatch;
std::shared_ptr<Sound> bestMatch;
if (Globals::gSettings.tabHotkeysOnly)
{
if (Globals::gData.isOnFavorites)
if (Globals::gData.onFavorites)
{
auto sounds = Globals::gData.getFavorites();
bestMatch = getBestMatch(sounds, pressedKeys);
@ -214,14 +200,14 @@ namespace Soundux
auto tab = Globals::gData.getTab(Globals::gSettings.selectedTab);
if (tab)
{
bestMatch = getBestMatch(tab->sounds, pressedKeys);
bestMatch = getBestMatch(tab->getSounds(), pressedKeys);
}
}
}
else
{
auto scopedSounds = Globals::gSounds.scoped();
bestMatch = getBestMatch(*scopedSounds, pressedKeys);
auto gSounds = Globals::gData.getSounds();
bestMatch = getBestMatch(gSounds, pressedKeys);
}
if (bestMatch)
@ -229,7 +215,7 @@ namespace Soundux
auto pSound = Globals::gGui->playSound(bestMatch->id);
if (pSound)
{
Globals::gGui->onSoundPlayed(*pSound);
Globals::gGui->onSoundPlayed(pSound);
}
}
}

View File

@ -30,7 +30,7 @@ namespace Soundux
std::atomic<bool> shouldNotifyKnob = false;
public:
static std::shared_ptr<Hotkeys> createInstance();
static std::unique_ptr<Hotkeys> createInstance();
public:
virtual void notify(bool);

View File

@ -1,37 +0,0 @@
#pragma once
#include <core/enums/enums.hpp>
#include <optional>
#include <string>
#include <vector>
namespace Soundux
{
namespace Objects
{
struct Key;
struct Sound
{
std::uint32_t id;
std::string name;
std::string path;
bool isFavorite = false;
std::vector<Key> hotkeys;
std::uint64_t modifiedDate;
std::optional<int> localVolume;
std::optional<int> remoteVolume;
};
struct Tab
{
std::uint32_t id; //* Equal to index
std::string name;
std::string path;
std::vector<Sound> sounds;
Enums::SortMode sortMode = Enums::SortMode::ModifiedDate_Descending;
};
} // namespace Objects
} // namespace Soundux

View File

@ -0,0 +1,67 @@
#include "sound.hpp"
namespace Soundux::Objects
{
Sound::Sound(std::uint64_t id, std::string name, std::string path)
: id(id), name(std::move(name)), path(std::move(path))
{
}
bool Sound::isFavorite() const
{
return favorite;
}
void Sound::markFavorite(bool state)
{
// TODO(): Modify gFavorites
favorite = state;
}
std::vector<Key> Sound::getHotkeys() const
{
return hotkeys.copy();
}
void Sound::setHotkeys(const std::vector<Key> &keys)
{
hotkeys.assign(keys);
}
void Sound::setModifiedDate(std::uint64_t newDate)
{
modifiedDate = newDate;
}
std::uint64_t Sound::getModifiedDate() const
{
return modifiedDate;
}
std::string Sound::getName() const
{
return name;
}
std::string Sound::getPath() const
{
return path;
}
std::uint64_t Sound::getId() const
{
return id;
}
std::optional<int> Sound::getLocalVolume() const
{
return localVolume.copy();
}
std::optional<int> Sound::getRemoteVolume() const
{
return localVolume.copy();
}
void Sound::setLocalVolume(std::optional<std::uint32_t> newVolume)
{
localVolume.assign(newVolume);
}
void Sound::setRemoteVolume(std::optional<std::uint32_t> newVolume)
{
remoteVolume.assign(newVolume);
}
} // namespace Soundux::Objects

View File

@ -0,0 +1,50 @@
#pragma once
#include <atomic>
#include <core/hotkeys/keys.hpp>
#include <cstdint>
#include <lock.hpp>
#include <optional>
#include <string>
#include <vector>
namespace Soundux
{
namespace Objects
{
struct Sound
{
std::uint64_t id;
std::string name;
std::string path;
sxl::lock<std::vector<Key>> hotkeys;
sxl::lock<std::optional<std::uint32_t>> localVolume;
sxl::lock<std::optional<std::uint32_t>> remoteVolume;
std::atomic<bool> favorite = false;
std::atomic<std::uint64_t> modifiedDate;
public:
Sound(std::uint64_t, std::string, std::string);
public:
bool isFavorite() const;
void markFavorite(bool);
std::vector<Key> getHotkeys() const;
void setHotkeys(const std::vector<Key> &);
void setModifiedDate(std::uint64_t);
std::uint64_t getModifiedDate() const;
std::string getName() const;
std::string getPath() const;
std::uint64_t getId() const;
std::optional<int> getLocalVolume() const;
std::optional<int> getRemoteVolume() const;
void setLocalVolume(std::optional<std::uint32_t>);
void setRemoteVolume(std::optional<std::uint32_t>);
};
} // namespace Objects
} // namespace Soundux

View File

@ -1,488 +0,0 @@
#include "audio.hpp"
#include <core/global/globals.hpp>
#include <fancy.hpp>
#if defined(_WIN32)
#include <helper/misc/misc.hpp>
#endif
#define MINIAUDIO_IMPLEMENTATION
#include <miniaudio.h>
namespace Soundux::Objects
{
#if defined(_WIN32)
using Soundux::Helpers::widen;
#endif
void Audio::setup()
{
#if defined(__linux__)
nullSink = std::nullopt;
#endif
for (const auto &device : getAudioDevices())
{
if (device.isDefault)
{
defaultPlayback = device;
}
#if defined(__linux__)
if (device.name == "soundux_sink")
{
nullSink = device;
}
#endif
}
}
void Audio::destroy()
{
stopAll();
}
std::optional<PlayingSound> Audio::play(const Objects::Sound &sound,
const std::optional<Objects::AudioDevice> &playbackDevice)
{
static std::atomic<std::uint64_t> id = 0;
auto *decoder = new ma_decoder;
#if defined(_WIN32)
auto res = ma_decoder_init_file_w(widen(sound.path).c_str(), nullptr, decoder);
#else
auto res = ma_decoder_init_file(sound.path.c_str(), nullptr, decoder);
#endif
if (res != MA_SUCCESS)
{
Fancy::fancy.logTime().failure() << "Failed to create decoder from file: " << sound.path << ", error: " >>
res << std::endl;
delete decoder;
return std::nullopt;
}
auto *device = new ma_device;
auto config = ma_device_config_init(ma_device_type_playback);
auto length_in_pcm_frames = ma_decoder_get_length_in_pcm_frames(decoder);
config.dataCallback = data_callback;
config.sampleRate = decoder->outputSampleRate;
config.playback.format = decoder->outputFormat;
config.playback.channels = decoder->outputChannels;
auto pSound = std::make_shared<PlayingSound>();
config.pUserData = reinterpret_cast<void *>(static_cast<PlayingSound *>(pSound.get()));
if (playbackDevice)
{
config.playback.pDeviceID = &playbackDevice->raw.id;
}
else
{
config.playback.pDeviceID = &defaultPlayback.raw.id;
}
if (ma_device_init(nullptr, &config, device) != MA_SUCCESS)
{
Fancy::fancy.logTime().failure() << "Failed to create device" << std::endl;
ma_decoder_uninit(decoder);
delete decoder;
delete device;
return std::nullopt;
}
if (playbackDevice)
{
if (sound.remoteVolume)
{
device->masterVolumeFactor = static_cast<float>(*sound.remoteVolume) / 100.f;
}
else
{
device->masterVolumeFactor = static_cast<float>(Globals::gSettings.remoteVolume) / 100.f;
}
}
else
{
if (sound.localVolume)
{
device->masterVolumeFactor = static_cast<float>(*sound.localVolume) / 100.f;
}
else
{
device->masterVolumeFactor = static_cast<float>(Globals::gSettings.localVolume) / 100.f;
}
}
if (ma_device_start(device) != MA_SUCCESS)
{
ma_device_uninit(device);
ma_decoder_uninit(decoder);
delete device;
delete decoder;
Fancy::fancy.logTime().warning() << "Failed to play sound " << sound.path << std::endl;
return std::nullopt;
}
auto soundId = ++id;
pSound->id = soundId;
pSound->sound = sound;
pSound->raw.device = device;
pSound->raw.decoder = decoder;
pSound->length = length_in_pcm_frames;
pSound->sampleRate = config.sampleRate;
pSound->playbackDevice = playbackDevice ? *playbackDevice : defaultPlayback;
pSound->lengthInMs = static_cast<std::uint64_t>(static_cast<double>(pSound->length) /
static_cast<double>(config.sampleRate) * 1000);
playingSounds->emplace(soundId, pSound);
return *pSound;
}
void Audio::stopAll()
{
auto scoped = playingSounds.scoped();
while (!scoped->empty())
{
auto &sound = scoped->begin()->second;
if (sound->raw.device && sound->raw.decoder)
{
ma_device_uninit(sound->raw.device);
ma_decoder_uninit(sound->raw.decoder);
}
sound->raw.device = nullptr;
sound->raw.decoder = nullptr;
scoped->erase(sound->id);
}
}
bool Audio::stop(const std::uint32_t &soundId)
{
auto scoped = playingSounds.scoped();
if (scoped->find(soundId) != scoped->end())
{
auto &sound = scoped->at(soundId);
if (sound->raw.device && sound->raw.decoder)
{
ma_device_uninit(sound->raw.device);
ma_decoder_uninit(sound->raw.decoder);
}
sound->raw.device = nullptr;
sound->raw.decoder = nullptr;
scoped->erase(sound->id);
return true;
}
Fancy::fancy.logTime().warning() << "Failed to stop sound with id " << soundId << ", sound does not exist"
<< std::endl;
return false;
}
std::optional<PlayingSound> Audio::pause(const std::uint32_t &soundId)
{
auto scoped = playingSounds.scoped();
if (scoped->find(soundId) != scoped->end())
{
auto &sound = scoped->at(soundId);
if (!sound->paused)
{
if (ma_device_get_state(sound->raw.device) == MA_STATE_STARTED)
{
ma_device_stop(sound->raw.device);
}
sound->paused = true;
}
return *sound;
}
Fancy::fancy.logTime().warning() << "Failed to pause sound with id " << soundId << ", sound does not exist"
<< std::endl;
return std::nullopt;
}
std::optional<PlayingSound> Audio::repeat(const std::uint32_t &soundId, bool shouldRepeat)
{
auto scoped = playingSounds.scoped();
if (scoped->find(soundId) != scoped->end())
{
auto &sound = scoped->at(soundId);
sound->repeat = shouldRepeat;
return *sound;
}
Fancy::fancy.logTime().warning() << "Failed to set repeat for sound with id " << soundId
<< ", sound does not exist" << std::endl;
return std::nullopt;
}
std::optional<PlayingSound> Audio::resume(const std::uint32_t &soundId)
{
auto scoped = playingSounds.scoped();
if (scoped->find(soundId) != scoped->end())
{
auto &sound = scoped->at(soundId);
if (sound->paused)
{
if (ma_device_get_state(sound->raw.device) == MA_STATE_STOPPED)
{
ma_device_start(sound->raw.device);
}
sound->paused = false;
}
return *sound;
}
Fancy::fancy.logTime().warning() << "Failed to resume sound with id " << soundId << ", sound does not exist "
<< std::endl;
return std::nullopt;
}
void Audio::onFinished(PlayingSound sound)
{
auto scoped = playingSounds.scoped();
if (scoped->find(sound.id) != scoped->end())
{
ma_device_uninit(sound.raw.device);
ma_decoder_uninit(sound.raw.decoder);
sound.raw.device = nullptr;
sound.raw.decoder = nullptr;
Globals::gGui->onSoundFinished(sound);
scoped->erase(sound.id);
}
else
{
Fancy::fancy.logTime().warning() << "Sound finished but is not playing" << std::endl;
}
}
void Audio::onSoundProgressed(PlayingSound *sound, std::uint64_t frames)
{
sound->readFrames += frames;
sound->buffer += frames;
if (sound->buffer > (sound->sampleRate / 2))
{
sound->readInMs = static_cast<std::uint64_t>(
(static_cast<double>(sound->readFrames) / static_cast<double>(sound->length)) *
static_cast<double>(sound->lengthInMs));
Globals::gGui->onSoundProgressed(*sound);
sound->buffer = 0;
}
}
void Audio::onSoundSeeked(PlayingSound *sound, std::uint64_t frame)
{
sound->shouldSeek = false;
sound->readFrames = frame;
sound->readInMs = static_cast<std::uint64_t>((static_cast<double>(frame) / static_cast<double>(sound->length)) *
static_cast<double>(sound->lengthInMs));
}
std::optional<PlayingSound> Audio::seek(const std::uint32_t &soundId, std::uint64_t position)
{
auto scoped = playingSounds.scoped();
if (scoped->find(soundId) != scoped->end())
{
auto &sound = scoped->at(soundId);
sound->seekTo =
static_cast<std::uint64_t>((static_cast<double>(position) / static_cast<double>(sound->lengthInMs)) *
static_cast<double>(sound->length));
sound->shouldSeek = true;
auto rtn = *sound;
rtn.readFrames = rtn.seekTo;
rtn.readInMs =
static_cast<std::uint64_t>((static_cast<double>(rtn.seekTo) / static_cast<double>(rtn.length)) *
static_cast<double>(rtn.lengthInMs));
return rtn;
}
Fancy::fancy.logTime().warning() << "Failed to seek sound with id " << soundId << ", sound does not exist"
<< std::endl;
return std::nullopt;
}
void Audio::data_callback(ma_device *device, void *output, [[maybe_unused]] const void *input,
std::uint32_t frameCount)
{
auto *sound = reinterpret_cast<PlayingSound *>(device->pUserData);
if (!sound)
{
return;
}
if (!sound->raw.decoder)
{
return;
}
auto readFrames = ma_decoder_read_pcm_frames(sound->raw.decoder, output, frameCount);
if (sound->shouldSeek)
{
ma_decoder_seek_to_pcm_frame(sound->raw.decoder, sound->seekTo);
Globals::gAudio.onSoundSeeked(sound, sound->seekTo);
}
if (sound->playbackDevice.isDefault && readFrames > 0)
{
Globals::gAudio.onSoundProgressed(sound, readFrames);
}
if (readFrames <= 0)
{
if (sound->repeat)
{
ma_decoder_seek_to_pcm_frame(sound->raw.decoder, 0);
Globals::gAudio.onSoundSeeked(sound, 0);
}
else
{
Globals::gQueue.push_unique(reinterpret_cast<std::uintptr_t>(device),
[sound = *sound] { Globals::gAudio.onFinished(sound); });
}
}
}
std::vector<AudioDevice> Audio::getAudioDevices()
{
std::string defaultName;
{
ma_device device;
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback);
ma_device_init(nullptr, &deviceConfig, &device);
defaultName = device.playback.name;
ma_device_uninit(&device);
}
ma_context context;
if (ma_context_init(nullptr, 0, nullptr, &context) != MA_SUCCESS)
{
Fancy::fancy.logTime().failure() << "Failed to initialize context" << std::endl;
return {};
}
ma_device_info *pPlayBackDeviceInfos{};
ma_uint32 deviceCount{};
ma_result result = ma_context_get_devices(&context, &pPlayBackDeviceInfos, &deviceCount, nullptr, nullptr);
if (result != MA_SUCCESS)
{
Fancy::fancy.logTime().failure() << "Failed to get playback devices!" << std::endl;
return {};
}
std::vector<AudioDevice> playBackDevices;
for (unsigned int i = 0; deviceCount > i; i++)
{
auto &rawDevice = pPlayBackDeviceInfos[i];
AudioDevice device;
device.raw = rawDevice;
device.name = rawDevice.name;
device.isDefault = rawDevice.name == defaultName;
playBackDevices.emplace_back(device);
}
ma_context_uninit(&context);
for (auto it = playBackDevices.begin(); it != playBackDevices.end(); it++)
{
if (it->name.find("VB-Audio") != std::string::npos)
{
if (it != playBackDevices.begin())
{
std::iter_swap(playBackDevices.begin(), it);
}
}
}
return playBackDevices;
}
#if defined(_WIN32)
std::optional<AudioDevice> Audio::getAudioDevice(const std::string &name)
{
for (const auto &device : getAudioDevices())
{
if (device.name == name)
{
return device;
}
}
return std::nullopt;
}
#endif
std::vector<PlayingSound> Audio::getPlayingSounds()
{
auto scoped = playingSounds.scoped();
std::vector<PlayingSound> rtn;
for (const auto &sound : *scoped)
{
rtn.emplace_back(*sound.second);
}
return rtn;
}
PlayingSound::PlayingSound(const PlayingSound &other)
{
if (&other == this)
{
return;
}
length = other.length;
lengthInMs = other.lengthInMs;
readFrames = other.readFrames;
sampleRate = other.sampleRate;
id = other.id;
sound = other.sound;
buffer = other.buffer;
seekTo.store(other.seekTo);
paused.store(other.paused);
repeat.store(other.repeat);
readInMs.store(other.readInMs);
shouldSeek.store(other.shouldSeek);
raw.device.store(other.raw.device);
raw.decoder.store(other.raw.decoder);
playbackDevice = other.playbackDevice;
}
PlayingSound &PlayingSound::operator=(const PlayingSound &other)
{
if (&other == this)
{
return *this;
}
length = other.length;
lengthInMs = other.lengthInMs;
readFrames = other.readFrames;
sampleRate = other.sampleRate;
id = other.id;
sound = other.sound;
buffer = other.buffer;
seekTo.store(other.seekTo);
paused.store(other.paused);
repeat.store(other.repeat);
readInMs.store(other.readInMs);
shouldSeek.store(other.shouldSeek);
raw.device.store(other.raw.device);
raw.decoder.store(other.raw.decoder);
playbackDevice = other.playbackDevice;
return *this;
}
} // namespace Soundux::Objects

View File

@ -1,88 +0,0 @@
#pragma once
#include <atomic>
#include <core/objects/objects.hpp>
#include <cstdint>
#include <map>
#include <memory>
#include <miniaudio.h>
#include <mutex>
#include <optional>
#include <string>
#include <var_guard.hpp>
namespace Soundux
{
namespace Objects
{
struct AudioDevice
{
ma_device_info raw;
std::string name;
bool isDefault;
};
struct PlayingSound
{
AudioDevice playbackDevice;
struct
{
std::atomic<ma_device *> device;
std::atomic<ma_decoder *> decoder;
} raw;
std::uint64_t length = 0;
std::uint64_t lengthInMs = 0;
std::uint64_t readFrames = 0;
std::uint64_t sampleRate = 0;
std::atomic<bool> paused = false;
std::atomic<bool> repeat = false;
std::atomic<bool> shouldSeek = false;
std::atomic<std::uint64_t> seekTo = 0;
std::atomic<std::uint64_t> readInMs = 0;
Sound sound;
std::uint32_t id;
std::uint64_t buffer = 0;
PlayingSound() = default;
PlayingSound(const PlayingSound &);
PlayingSound &operator=(const PlayingSound &other);
};
class Audio
{
sxl::var_guard<std::map<std::uint32_t, std::shared_ptr<PlayingSound>>, std::recursive_mutex> playingSounds;
void onFinished(PlayingSound);
void onSoundSeeked(PlayingSound *, std::uint64_t);
void onSoundProgressed(PlayingSound *, std::uint64_t);
static void data_callback(ma_device *device, void *output, const void *input, std::uint32_t frameCount);
public:
std::optional<PlayingSound> pause(const std::uint32_t &);
std::optional<PlayingSound> resume(const std::uint32_t &);
std::optional<PlayingSound> repeat(const std::uint32_t &, bool);
std::optional<PlayingSound> seek(const std::uint32_t &, std::uint64_t);
std::optional<PlayingSound> play(const Objects::Sound &, const std::optional<AudioDevice> & = std::nullopt);
std::vector<AudioDevice> getAudioDevices();
std::vector<Objects::PlayingSound> getPlayingSounds();
#if defined(_WIN32)
std::optional<AudioDevice> getAudioDevice(const std::string &);
#endif
void setup();
void destroy();
void stopAll();
bool stop(const std::uint32_t &);
#if defined(__linux__)
std::optional<AudioDevice> nullSink;
#endif
AudioDevice defaultPlayback;
};
} // namespace Objects
} // namespace Soundux

View File

@ -0,0 +1,76 @@
#include "device.hpp"
#include <fancy.hpp>
namespace Soundux::Objects
{
std::vector<AudioDevice> AudioDevice::getDevices()
{
std::string defaultName;
{
ma_device device;
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback);
ma_device_init(nullptr, &deviceConfig, &device);
defaultName = device.playback.name;
ma_device_uninit(&device);
}
ma_context context;
if (ma_context_init(nullptr, 0, nullptr, &context) != MA_SUCCESS)
{
Fancy::fancy.logTime().failure() << "Failed to initialize context" << std::endl;
return {};
}
ma_device_info *pPlayBackDeviceInfos{};
ma_uint32 deviceCount{};
ma_result result = ma_context_get_devices(&context, &pPlayBackDeviceInfos, &deviceCount, nullptr, nullptr);
if (result != MA_SUCCESS)
{
Fancy::fancy.logTime().failure() << "Failed to get playback devices!" << std::endl;
return {};
}
std::vector<AudioDevice> playBackDevices;
for (unsigned int i = 0; deviceCount > i; i++)
{
auto &rawDevice = pPlayBackDeviceInfos[i];
AudioDevice device;
device.raw = rawDevice;
device.name = rawDevice.name;
device.isDefault = rawDevice.name == defaultName;
playBackDevices.emplace_back(device);
}
ma_context_uninit(&context);
for (auto it = playBackDevices.begin(); it != playBackDevices.end(); it++)
{
if (it->name.find("VB-Audio") != std::string::npos)
{
if (it != playBackDevices.begin())
{
std::iter_swap(playBackDevices.begin(), it);
}
}
}
return playBackDevices;
}
std::optional<AudioDevice> AudioDevice::getDeviceByName(const std::string &name)
{
for (const auto &device : getDevices())
{
if (device.name == name)
{
return device;
}
}
return std::nullopt;
}
} // namespace Soundux::Objects

View File

@ -0,0 +1,22 @@
#pragma once
#include <miniaudio.h>
#include <optional>
#include <string>
#include <vector>
namespace Soundux
{
namespace Objects
{
struct AudioDevice
{
bool isDefault;
std::string name;
ma_device_info raw;
public:
static std::vector<AudioDevice> getDevices();
static std::optional<AudioDevice> getDeviceByName(const std::string &);
};
} // namespace Objects
} // namespace Soundux

View File

@ -19,24 +19,19 @@ namespace Soundux::Objects
if (pulseInstance && pulseInstance->setup())
{
if (!pulseInstance->switchOnConnectPresent())
if (pulseInstance->isRunningPipeWire())
{
if (pulseInstance->loadModules())
{
return instance;
}
backend = Enums::BackendType::PipeWire;
Globals::gSettings.audioBackend = backend;
pulseInstance->destroy();
pulseInstance.reset();
}
else
{
return instance;
}
}
if (pulseInstance && pulseInstance->isRunningPipeWire())
{
backend = Enums::BackendType::PipeWire;
Globals::gSettings.audioBackend = backend;
}
}
if (backend == Enums::BackendType::PipeWire)

View File

@ -45,13 +45,10 @@ namespace Soundux
virtual bool stopAllPassthrough() = 0;
virtual bool stopPassthrough(const std::string &) = 0;
virtual bool passthroughFrom(std::shared_ptr<PlaybackApp>) = 0;
virtual bool passthroughFrom(const std::string &) = 0;
virtual bool stopSoundInput() = 0;
virtual bool inputSoundTo(std::shared_ptr<RecordingApp>) = 0;
virtual std::shared_ptr<PlaybackApp> getPlaybackApp(const std::string &) = 0;
virtual std::shared_ptr<RecordingApp> getRecordingApp(const std::string &) = 0;
virtual bool inputSoundTo(const std::string &) = 0;
virtual std::vector<std::shared_ptr<PlaybackApp>> getPlaybackApps() = 0;
virtual std::vector<std::shared_ptr<RecordingApp>> getRecordingApps() = 0;

View File

@ -56,9 +56,12 @@ namespace Soundux::Objects
void PipeWire::onNodeInfo(const pw_node_info *info)
{
if (info && (~nodes).find(info->id) != (~nodes).end())
auto &nodes = this->nodes.unsafe();
if (info && nodes.find(info->id) != nodes.end())
{
auto &self = (~nodes).at(info->id);
auto &self = nodes.at(info->id);
self.inputPorts = info->n_input_ports;
self.outputPorts = info->n_output_ports;
if (const auto *pid = spa_dict_lookup(info->props, "application.process.id"); pid)
{
@ -69,7 +72,6 @@ namespace Soundux::Objects
self.isMonitor = true;
}
//* Yes this is swapped. (For compatibility reasons)
if (const auto *appName = spa_dict_lookup(info->props, "application.name"); appName)
{
self.name = appName;
@ -81,41 +83,6 @@ namespace Soundux::Objects
}
}
void PipeWire::onPortInfo(const pw_port_info *info)
{
if (info && (~ports).find(info->id) != (~ports).end())
{
auto &self = (~ports).at(info->id);
self.direction = info->direction;
if (const auto *nodeId = spa_dict_lookup(info->props, "node.id"); nodeId)
{
self.parentNode = std::stol(nodeId);
}
if (const auto *rawPortName = spa_dict_lookup(info->props, "port.name"); rawPortName)
{
auto portName = std::string(rawPortName);
if (portName.back() == '1' || portName.back() == 'L')
{
self.side = Side::LEFT;
}
else if (portName.back() == '2' || portName.back() == 'R')
{
self.side = Side::RIGHT;
}
else if (portName.find("MONO", portName.size() - 4) != std::string::npos)
{
self.side = Side::MONO;
}
}
if (const auto *portAlias = spa_dict_lookup(info->props, "port.alias"); portAlias)
{
self.portAlias = std::string(portAlias);
}
}
}
void PipeWire::onCoreInfo(const pw_core_info *info)
{
if (info && info->name && info->version && !version)
@ -206,7 +173,7 @@ namespace Soundux::Objects
if (strcmp(type, PW_TYPE_INTERFACE_Node) == 0)
{
const auto *name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
if (name && strstr(name, "soundux"))
if (name && strcmp(name, "soundux") == 0)
{
return;
}
@ -231,55 +198,14 @@ namespace Soundux::Objects
if (boundNode)
{
thiz->nodes->emplace(id, node);
//* We lock the mutex here, because we only need access to `nodes` as soon as sync is called.
thiz->nodes.write()->emplace(id, node);
pw_node_add_listener(boundNode, &listener, &events, thiz); // NOLINT
thiz->sync();
spa_hook_remove(&listener);
PipeWireApi::proxy_destroy(reinterpret_cast<pw_proxy *>(boundNode));
}
}
if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0)
{
Port port;
port.id = id;
spa_hook listener;
pw_port_events events = {};
events.info = [](void *data, const pw_port_info *info) {
auto *thiz = reinterpret_cast<PipeWire *>(data);
if (thiz)
{
thiz->onPortInfo(info);
}
};
events.version = PW_VERSION_PORT_EVENTS;
auto *boundPort =
reinterpret_cast<pw_port *>(pw_registry_bind(thiz->registry, id, type, version, sizeof(PipeWire)));
if (boundPort)
{
thiz->ports->emplace(id, port);
pw_port_add_listener(boundPort, &listener, &events, thiz); // NOLINT
thiz->sync();
spa_hook_remove(&listener);
PipeWireApi::proxy_destroy(reinterpret_cast<pw_proxy *>(boundPort));
auto scopedNodes = thiz->nodes.scoped();
auto scopedPorts = thiz->ports.scoped();
if (scopedPorts->find(id) != scopedPorts->end())
{
auto &port = scopedPorts->at(id);
if (port.parentNode > 0 && scopedNodes->find(port.parentNode) != scopedNodes->end())
{
auto &node = scopedNodes->at(port.parentNode);
node.ports.emplace(id, port);
scopedPorts->erase(id);
}
}
}
}
}
}
@ -288,16 +214,10 @@ namespace Soundux::Objects
auto *thiz = reinterpret_cast<PipeWire *>(data);
if (thiz)
{
auto scopedNodes = thiz->nodes.scoped();
if (scopedNodes->find(id) != scopedNodes->end())
auto nodes = thiz->nodes.write();
if (nodes->find(id) != nodes->end())
{
scopedNodes->erase(id);
}
auto scopedPorts = thiz->ports.scoped();
if (scopedPorts->find(id) != scopedPorts->end())
{
scopedPorts->erase(id);
nodes->erase(id);
}
}
}
@ -404,13 +324,15 @@ namespace Soundux::Objects
return true;
}
std::optional<int> PipeWire::linkPorts(std::uint32_t in, std::uint32_t out)
std::optional<int> PipeWire::createLink(std::uint32_t in, std::uint32_t out)
{
pw_properties *props = PipeWireApi::properties_new(nullptr, nullptr);
PipeWireApi::properties_set(props, PW_KEY_APP_NAME, "soundux");
PipeWireApi::properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", in);
PipeWireApi::properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", out);
PipeWireApi::properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%i", -1);
PipeWireApi::properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%i", -1);
PipeWireApi::properties_set(props, PW_KEY_LINK_INPUT_NODE, std::to_string(in).c_str());
PipeWireApi::properties_set(props, PW_KEY_LINK_OUTPUT_NODE, std::to_string(out).c_str());
auto *proxy = reinterpret_cast<pw_proxy *>(
pw_core_create_object(core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0));
@ -430,9 +352,9 @@ namespace Soundux::Objects
linkEvent.bound = [](void *data, std::uint32_t id) {
*reinterpret_cast<std::optional<std::uint32_t> *>(data) = id;
};
linkEvent.error = [](void *data, [[maybe_unused]] int a, [[maybe_unused]] int b, const char *message) {
Fancy::fancy.logTime().warning() << "Failed to create link: " << message << std::endl;
*reinterpret_cast<std::optional<std::uint32_t> *>(data) = std::nullopt;
linkEvent.error = []([[maybe_unused]] void *data, int seq, int res, const char *message) {
Fancy::fancy.logTime().warning()
<< "An error occurred while creating a link(" << seq << ":" << res << "): " << message << std::endl;
};
PipeWireApi::proxy_add_listener(proxy, &listener, &linkEvent, &result);
@ -449,26 +371,15 @@ namespace Soundux::Objects
sync();
std::vector<std::shared_ptr<RecordingApp>> rtn;
auto scopedNodes = nodes.scoped();
for (const auto &[nodeId, node] : *scopedNodes)
auto nodes = this->nodes.read();
for (const auto &[nodeId, node] : *nodes)
{
if (!node.name.empty() && !node.isMonitor)
{
bool hasInput = false;
for (const auto &[portId, port] : node.ports)
{
if (port.direction == SPA_DIRECTION_INPUT)
{
hasInput = true;
break;
}
}
if (hasInput)
if (node.inputPorts)
{
PipeWireRecordingApp app;
app.pid = node.pid;
app.nodeId = nodeId;
app.node = node;
app.name = node.name;
app.application = node.applicationBinary;
rtn.emplace_back(std::make_shared<PipeWireRecordingApp>(app));
@ -484,26 +395,15 @@ namespace Soundux::Objects
sync();
std::vector<std::shared_ptr<PlaybackApp>> rtn;
auto scopedNodes = nodes.scoped();
for (const auto &[nodeId, node] : *scopedNodes)
auto nodes = this->nodes.read();
for (const auto &[nodeId, node] : *nodes)
{
if (!node.name.empty() && !node.isMonitor)
{
bool hasOutput = false;
for (const auto &[portId, port] : node.ports)
{
if (port.direction == SPA_DIRECTION_OUTPUT)
{
hasOutput = true;
break;
}
}
if (hasOutput)
if (node.outputPorts)
{
PipeWirePlaybackApp app;
app.pid = node.pid;
app.nodeId = nodeId;
app.node = node;
app.name = node.name;
app.application = node.applicationBinary;
rtn.emplace_back(std::make_shared<PipeWirePlaybackApp>(app));
@ -514,44 +414,6 @@ namespace Soundux::Objects
return rtn;
}
std::shared_ptr<PlaybackApp> PipeWire::getPlaybackApp(const std::string &app)
{
auto scopedNodes = nodes.scoped();
for (const auto &[nodeId, node] : *scopedNodes)
{
if (node.applicationBinary == app)
{
PipeWirePlaybackApp app;
app.pid = node.pid;
app.nodeId = nodeId;
app.name = node.name;
app.application = node.applicationBinary;
return std::make_shared<PipeWirePlaybackApp>(app);
}
}
return nullptr;
}
std::shared_ptr<RecordingApp> PipeWire::getRecordingApp(const std::string &app)
{
auto scopedNodes = nodes.scoped();
for (const auto &[nodeId, node] : *scopedNodes)
{
if (node.applicationBinary == app)
{
PipeWireRecordingApp app;
app.pid = node.pid;
app.nodeId = nodeId;
app.name = node.name;
app.application = node.applicationBinary;
return std::make_shared<PipeWireRecordingApp>(app);
}
}
return nullptr;
}
bool PipeWire::useAsDefault()
{
// TODO(pipewire): Find a way to connect the output to the microphone
@ -608,69 +470,52 @@ namespace Soundux::Objects
return false;
}
bool PipeWire::inputSoundTo(std::shared_ptr<RecordingApp> app)
bool PipeWire::inputSoundTo(const std::string &app)
{
if (!app)
sync();
if (app.empty())
{
Fancy::fancy.logTime().warning() << "Invalid app" << std::endl;
return false;
}
if (soundInputLinks.count(app->application))
if (soundInputLinks.count(app))
{
return true;
}
auto pipeWireApp = std::dynamic_pointer_cast<PipeWireRecordingApp>(app);
if (!pipeWireApp)
{
Fancy::fancy.logTime().warning() << "Supplied app was not a Recording App" << std::endl;
return false;
}
bool success = false;
auto nodes = this->nodes.copy();
auto ports = this->ports.copy();
if (!soundInputLinks.count(app->application))
{
soundInputLinks.emplace(app->application, std::vector<std::uint32_t>{});
}
std::vector<std::uint32_t> createdLinks;
for (const auto &[nodeId, node] : nodes)
{
if (node.applicationBinary != app->application)
continue;
for (const auto &[portId, port] : ports)
if (node.rawName == "soundux_sink")
{
if (port.direction == SPA_DIRECTION_OUTPUT && port.portAlias.find("soundux") != std::string::npos)
for (const auto &[appNodeId, appNode] : nodes)
{
for (const auto &[nodePortId, nodePort] : node.ports)
if (appNode.applicationBinary == app && appNode.inputPorts)
{
if (nodePort.direction == SPA_DIRECTION_INPUT)
{
if (nodePort.side == Side::UNDEFINED || port.side == Side::UNDEFINED)
continue;
if (nodePort.side == port.side || nodePort.side == Side::MONO)
{
auto link = linkPorts(nodePortId, portId);
auto link = createLink(appNodeId, nodeId);
if (link)
{
success = true;
soundInputLinks.at(app->application).emplace_back(*link);
}
}
createdLinks.emplace_back(*link);
}
}
}
}
}
if (!success)
if (success)
{
Fancy::fancy.logTime().warning() << "Could not find ports for app " << app->application << std::endl;
soundInputLinks.emplace(app, createdLinks);
}
else
{
Fancy::fancy.logTime().warning() << "Could not link app " << app << std::endl;
}
return success;
@ -690,65 +535,47 @@ namespace Soundux::Objects
return true;
}
bool PipeWire::passthroughFrom(std::shared_ptr<PlaybackApp> app)
bool PipeWire::passthroughFrom(const std::string &app)
{
if (!app)
sync();
if (app.empty())
{
Fancy::fancy.logTime().warning() << "Invalid app" << std::endl;
return false;
}
auto pipeWireApp = std::dynamic_pointer_cast<PipeWirePlaybackApp>(app);
if (!pipeWireApp)
{
Fancy::fancy.logTime().warning() << "Supplied app was not a Playback App" << std::endl;
return false;
}
bool success = false;
auto nodes = this->nodes.copy();
auto ports = this->ports.copy();
if (!passthroughLinks.count(app->application))
{
passthroughLinks.emplace(app->application, std::vector<std::uint32_t>{});
}
std::vector<std::uint32_t> createdLinks;
for (const auto &[nodeId, node] : nodes)
{
if (node.applicationBinary != app->application)
continue;
for (const auto &[portId, port] : ports)
if (node.rawName == "soundux_sink")
{
if (port.direction == SPA_DIRECTION_INPUT && port.portAlias.find("soundux") != std::string::npos)
for (const auto &[appNodeId, appNode] : nodes)
{
for (const auto &[nodePortId, nodePort] : node.ports)
if (appNode.applicationBinary == app && appNode.outputPorts)
{
if (nodePort.side == Side::UNDEFINED || port.side == Side::UNDEFINED)
continue;
if (nodePort.direction == SPA_DIRECTION_OUTPUT)
{
if (nodePort.side == port.side || nodePort.side == Side::MONO)
{
auto link = linkPorts(portId, nodePortId);
auto link = createLink(nodeId, appNodeId);
if (link)
{
success = true;
passthroughLinks.at(app->application).emplace_back(*link);
}
}
createdLinks.emplace_back(*link);
}
}
}
}
}
if (!success)
if (success)
{
Fancy::fancy.logTime().warning() << "Could not find ports for app " << app->application << std::endl;
passthroughLinks.emplace(app, createdLinks);
}
else
{
Fancy::fancy.logTime().warning() << "Could not link app " << app << std::endl;
}
return success;

View File

@ -1,8 +1,8 @@
#if defined(__linux__)
#include "../backend.hpp"
#include <lock.hpp>
#include <map>
#include <optional>
#include <var_guard.hpp>
#include <pipewire/extensions/metadata.h>
#include <pipewire/global.h>
@ -10,34 +10,10 @@
#include <spa/param/props.h>
#include <spa/pod/builder.h>
// TODO(pipewire):
//* From the pipewire news of 0.3.26
//* - The link factory can now also make links between nodes and
//* ports by name so that it can be used in scripts.
//*
//* Maybe we could try to make use of that, it could reduce loc.
namespace Soundux
{
namespace Objects
{
enum class Side
{
UNDEFINED,
LEFT,
RIGHT,
MONO,
};
struct Port
{
std::uint32_t id;
std::string portAlias;
spa_direction direction;
Side side = Side::UNDEFINED;
std::uint32_t parentNode = 0;
};
struct Node
{
std::uint32_t id;
@ -45,20 +21,19 @@ namespace Soundux
std::uint32_t pid;
std::string rawName;
bool isMonitor = false;
std::uint32_t inputPorts;
std::uint32_t outputPorts;
std::string applicationBinary;
std::map<std::uint32_t, Port> ports;
};
struct PipeWirePlaybackApp : public PlaybackApp
{
std::uint32_t pid;
std::uint32_t nodeId;
Node node;
~PipeWirePlaybackApp() override = default;
};
struct PipeWireRecordingApp : public RecordingApp
{
std::uint32_t pid;
std::uint32_t nodeId;
Node node;
~PipeWireRecordingApp() override = default;
};
@ -78,11 +53,9 @@ namespace Soundux
pw_registry_events registryEvents;
private:
sxl::var_guard<std::map<std::uint32_t, Node>> nodes;
sxl::var_guard<std::map<std::uint32_t, Port>> ports;
sxl::lock<std::map<std::uint32_t, Node>> nodes;
void onNodeInfo(const pw_node_info *);
void onPortInfo(const pw_port_info *);
void onCoreInfo(const pw_core_info *);
private:
@ -93,7 +66,7 @@ namespace Soundux
void sync();
bool createNullSink();
bool deleteLink(std::uint32_t);
std::optional<int> linkPorts(std::uint32_t, std::uint32_t);
std::optional<int> createLink(std::uint32_t, std::uint32_t);
static void onGlobalRemoved(void *, std::uint32_t);
static void onGlobalAdded(void *, std::uint32_t, std::uint32_t, const char *, std::uint32_t,
@ -115,13 +88,10 @@ namespace Soundux
bool stopAllPassthrough() override;
bool stopPassthrough(const std::string &app) override;
bool passthroughFrom(std::shared_ptr<PlaybackApp> app) override;
bool passthroughFrom(const std::string &app) override;
bool stopSoundInput() override;
bool inputSoundTo(std::shared_ptr<RecordingApp> app) override;
std::shared_ptr<PlaybackApp> getPlaybackApp(const std::string &app) override;
std::shared_ptr<RecordingApp> getRecordingApp(const std::string &app) override;
bool inputSoundTo(const std::string &app) override;
std::vector<std::shared_ptr<PlaybackApp>> getPlaybackApps() override;
std::vector<std::shared_ptr<RecordingApp>> getRecordingApps() override;

View File

@ -58,9 +58,49 @@ namespace Soundux::Objects
}
unloadLeftOvers();
unloadProblematic();
fetchDefaultSource();
return !(defaultSource.empty() || serverName.empty() || isRunningPipeWire());
if (defaultSource.empty() || serverName.empty() || isRunningPipeWire())
{
return false;
}
return loadModules();
}
void PulseAudio::unloadProblematic()
{
await(PulseApi::context_get_module_info_list(
context,
[]([[maybe_unused]] pa_context *ctx, const pa_module_info *info, [[maybe_unused]] int eol, void *userData) {
if (info && info->name && userData)
{
auto name = std::string(info->name);
auto *thiz = reinterpret_cast<PulseAudio *>(userData);
if (name.find("switch-on-connect") != std::string::npos ||
name.find("role-cork") != std::string::npos)
{
PulseModule module;
module.name = name;
if (info->argument)
module.arguments = info->argument;
thiz->unloadedModules.emplace_back(module);
Fancy::fancy.logTime().message() << "Unloading problematic module: " << name << std::endl;
}
}
},
this));
}
void PulseAudio::reloadProblematic()
{
for (auto &module : unloadedModules)
{
Fancy::fancy.logTime().message() << "Loading back module: " << module.name << std::endl;
PulseApi::context_load_module(context, module.name.c_str(), module.arguments.c_str(), nullptr, nullptr);
}
}
bool PulseAudio::loadModules()
{
@ -175,6 +215,8 @@ namespace Soundux::Objects
await(PulseApi::context_unload_module(context, *passthroughSink, nullptr, nullptr));
if (passthroughLoopBack)
await(PulseApi::context_unload_module(context, *passthroughLoopBack, nullptr, nullptr));
reloadProblematic();
}
void PulseAudio::await(pa_operation *operation)
{
@ -380,26 +422,27 @@ namespace Soundux::Objects
return true;
}
bool PulseAudio::passthroughFrom(std::shared_ptr<PlaybackApp> app)
bool PulseAudio::passthroughFrom(const std::string &app)
{
if (movedPassthroughApplications.count(app->application))
if (movedPassthroughApplications.count(app))
{
Fancy::fancy.logTime().message()
<< "Ignoring sound passthrough request because requested app is already moved" << std::endl;
return true;
}
if (!app)
if (app.empty())
{
Fancy::fancy.logTime().warning() << "Tried to passthrough to non existant app" << std::endl;
return false;
}
std::uint32_t originalSink = 0;
for (const auto &playbackApp : getPlaybackApps())
{
auto pulsePlayback = std::dynamic_pointer_cast<PulsePlaybackApp>(playbackApp);
if (playbackApp->application == app->application)
if (playbackApp->application == app)
{
bool success = true;
@ -419,10 +462,12 @@ namespace Soundux::Objects
<< "Failed top move " << pulsePlayback->id << " to passthrough" << std::endl;
return false;
}
originalSink = pulsePlayback->sink;
}
}
movedPassthroughApplications.emplace(app->application, std::dynamic_pointer_cast<PulsePlaybackApp>(app)->sink);
movedPassthroughApplications.emplace(app, originalSink);
return true;
}
bool PulseAudio::stopAllPassthrough()
@ -491,24 +536,25 @@ namespace Soundux::Objects
Fancy::fancy.logTime().warning() << "Could not find moved application " << app << std::endl;
return false;
}
bool PulseAudio::inputSoundTo(std::shared_ptr<RecordingApp> app)
bool PulseAudio::inputSoundTo(const std::string &app)
{
if (!app)
if (app.empty())
{
Fancy::fancy.logTime().warning() << "Tried to input sound to non existant app" << std::endl;
return false;
}
if (movedApplications.find(app->application) != movedApplications.end())
if (movedApplications.find(app) != movedApplications.end())
{
return true;
}
std::uint32_t originalSource = 0;
for (const auto &recordingApp : getRecordingApps())
{
auto pulseApp = std::dynamic_pointer_cast<PulseRecordingApp>(recordingApp);
if (pulseApp->application == app->application)
if (pulseApp->application == app)
{
bool success = true;
await(PulseApi::context_move_source_output_by_name(
@ -526,10 +572,14 @@ namespace Soundux::Objects
Fancy::fancy.logTime().warning() << "Failed to move " + pulseApp->application << "(" << pulseApp->id
<< ") to soundux sink" << std::endl;
}
else
{
originalSource = pulseApp->source;
}
}
}
movedApplications.emplace(app->application, std::dynamic_pointer_cast<PulseRecordingApp>(app)->source);
movedApplications.emplace(app, originalSource);
return true;
}
bool PulseAudio::stopSoundInput()
@ -566,30 +616,6 @@ namespace Soundux::Objects
return success;
}
std::shared_ptr<PlaybackApp> PulseAudio::getPlaybackApp(const std::string &application)
{
for (auto app : getPlaybackApps())
{
if (app->application == application)
{
return app;
}
}
return nullptr;
}
std::shared_ptr<RecordingApp> PulseAudio::getRecordingApp(const std::string &application)
{
for (auto app : getRecordingApps())
{
if (app->application == application)
{
return app;
}
}
return nullptr;
}
void PulseAudio::fixPlaybackApps(const std::vector<std::shared_ptr<PlaybackApp>> &originalPlayback)
{
@ -656,56 +682,6 @@ namespace Soundux::Objects
return success;
}
bool PulseAudio::switchOnConnectPresent()
{
bool isPresent = false;
await(PulseApi::context_get_module_info_list(
context,
[]([[maybe_unused]] pa_context *ctx, const pa_module_info *info, [[maybe_unused]] int eol, void *userData) {
if (info && info->name)
{
if (std::string(info->name).find("switch-on-connect") != std::string::npos)
{
*reinterpret_cast<bool *>(userData) = true;
Fancy::fancy.logTime().warning() << "Switch on connect found: " << info->index << std::endl;
}
}
},
&isPresent));
if (isPresent)
{
if (Globals::gGui)
{
Globals::gGui->onSwitchOnConnectDetected(true);
}
}
return isPresent;
}
void PulseAudio::unloadSwitchOnConnect()
{
await(PulseApi::context_get_module_info_list(
context,
[]([[maybe_unused]] pa_context *ctx, const pa_module_info *info, [[maybe_unused]] int eol,
[[maybe_unused]] void *userData) {
if (info && info->name)
{
if (std::string(info->name).find("switch-on-connect") != std::string::npos)
{
Fancy::fancy.logTime().message() << "Unloading: " << info->index << std::endl;
PulseApi::context_unload_module(ctx, info->index, nullptr, nullptr);
if (Globals::gGui)
{
Globals::gGui->onSwitchOnConnectDetected(false);
}
}
}
},
nullptr));
}
bool PulseAudio::isRunningPipeWire()
{
//* The stuff we do here does is broken on pipewire-pulse, use the native backend instead.

View File

@ -27,6 +27,12 @@ namespace Soundux
~PulseRecordingApp() override = default;
};
struct PulseModule
{
std::string name;
std::string arguments;
};
class PulseAudio : public AudioBackend
{
friend class AudioBackend;
@ -47,6 +53,7 @@ namespace Soundux
//* ~= ~~~~~~~~~~~~~~~~~~~~~ =~
std::string serverName;
std::vector<PulseModule> unloadedModules;
std::string defaultSource;
std::optional<std::uint32_t> defaultSourceId;
@ -56,11 +63,15 @@ namespace Soundux
std::mutex operationMutex;
bool loadModules();
void unloadLeftOvers();
void fetchDefaultSource();
void fetchLoopBackSinkId();
void await(pa_operation *);
void unloadProblematic();
void reloadProblematic();
void fixPlaybackApps(const std::vector<std::shared_ptr<PlaybackApp>> &);
void fixRecordingApps(const std::vector<std::shared_ptr<RecordingApp>> &);
@ -68,9 +79,6 @@ namespace Soundux
bool setup() override;
public:
//! Is not ran by default to avoid problems with switch-on-connect
bool loadModules();
void destroy() override;
bool isRunningPipeWire();
@ -83,16 +91,10 @@ namespace Soundux
bool stopAllPassthrough() override;
bool stopPassthrough(const std::string &app) override;
bool passthroughFrom(std::shared_ptr<PlaybackApp> app) override;
bool passthroughFrom(const std::string &app) override;
bool stopSoundInput() override;
bool inputSoundTo(std::shared_ptr<RecordingApp> app) override;
void unloadSwitchOnConnect();
bool switchOnConnectPresent();
std::shared_ptr<PlaybackApp> getPlaybackApp(const std::string &application) override;
std::shared_ptr<RecordingApp> getRecordingApp(const std::string &application) override;
bool inputSoundTo(const std::string &app) override;
std::vector<std::shared_ptr<PlaybackApp>> getPlaybackApps() override;
std::vector<std::shared_ptr<RecordingApp>> getRecordingApps() override;

View File

@ -0,0 +1,255 @@
#include "sound.hpp"
#include <core/global/globals.hpp>
#include <core/objects/sound.hpp>
#include <fancy.hpp>
#define MINIAUDIO_IMPLEMENTATION
#include <miniaudio.h>
namespace Soundux::Objects
{
std::atomic<std::uint64_t> PlayingSound::idCounter = 0;
std::shared_ptr<PlayingSound> PlayingSound::create(const std::shared_ptr<Sound> &sound,
const AudioDevice &playbackDevice)
{
auto rtn = std::shared_ptr<PlayingSound>(new PlayingSound());
rtn->sound = sound;
rtn->id = ++idCounter;
rtn->playbackDevice = playbackDevice;
auto device = rtn->device.write();
auto decoder = rtn->decoder.write();
*device = std::make_shared<ma_device>();
*decoder = std::make_shared<ma_decoder>();
// TODO(): Use wide path universally
#if defined(_WIN32)
auto res = ma_decoder_init_file_w(widen(sound.path).c_str(), nullptr, &*decoder);
#else
auto res = ma_decoder_init_file(sound->getPath().c_str(), nullptr, decoder->get());
#endif
if (res != MA_SUCCESS)
{
Fancy::fancy.logTime().failure()
<< "Failed to create decoder from file: " << sound->getPath() << " (" << res << ")" << std::endl;
return nullptr;
}
rtn->sampleRate = decoder->get()->outputSampleRate;
rtn->length = ma_decoder_get_length_in_pcm_frames(decoder->get());
auto config = ma_device_config_init(ma_device_type_playback);
config.pUserData = rtn.get();
config.dataCallback = data_callback;
config.sampleRate = rtn->sampleRate;
config.playback.format = decoder->get()->outputFormat;
config.playback.pDeviceID = &rtn->playbackDevice.raw.id;
config.playback.channels = decoder->get()->outputChannels;
if (ma_device_init(nullptr, &config, device->get()) != MA_SUCCESS)
{
Fancy::fancy.logTime().failure() << "Failed to create device" << std::endl;
ma_decoder_uninit(decoder->get());
return nullptr;
}
if (playbackDevice.isDefault)
{
if (sound->getLocalVolume())
{
device->get()->masterVolumeFactor = static_cast<float>(*sound->getLocalVolume()) / 100.f;
}
else
{
device->get()->masterVolumeFactor = static_cast<float>(Globals::gSettings.localVolume) / 100.f;
}
}
else
{
if (sound->getRemoteVolume())
{
device->get()->masterVolumeFactor = static_cast<float>(*sound->getRemoteVolume()) / 100.f;
}
else
{
device->get()->masterVolumeFactor = static_cast<float>(Globals::gSettings.remoteVolume) / 100.f;
}
}
if (ma_device_start(device->get()) != MA_SUCCESS)
{
ma_device_uninit(device->get());
ma_decoder_uninit(decoder->get());
Fancy::fancy.logTime().warning() << "Failed to start device for sound " << sound->getPath() << std::endl;
return nullptr;
}
return rtn;
}
PlayingSound::~PlayingSound()
{
if (device.copy())
{
stop();
}
}
void PlayingSound::stop()
{
if (device.copy() && decoder.copy())
{
auto device = this->device.write();
auto decoder = this->decoder.write();
done = true;
ma_device_uninit(device->get());
ma_decoder_uninit(decoder->get());
*device = nullptr;
*decoder = nullptr;
}
}
void PlayingSound::pause()
{
if (device.copy())
{
auto device = this->device.write();
if (ma_device_get_state(device->get()) == MA_STATE_STARTED)
{
ma_device_stop(device->get());
}
else
{
Fancy::fancy.logTime().warning()
<< "Tried to pause sound " << id << " which is not playing" << std::endl;
}
}
}
bool PlayingSound::isPaused() const
{
auto device = this->device.read();
return ma_device_get_state(device->get()) == MA_STATE_STOPPED;
}
void PlayingSound::resume()
{
if (device.copy())
{
auto device = this->device.write();
if (ma_device_get_state(device->get()) == MA_STATE_STOPPED)
{
ma_device_start(device->get());
}
else
{
Fancy::fancy.logTime().warning()
<< "Tried to resume sound " << id << " which is not paused" << std::endl;
}
}
}
void PlayingSound::repeat(bool state)
{
shouldRepeat = state;
}
std::uint64_t PlayingSound::getLength() const
{
return static_cast<std::uint64_t>(static_cast<double>(length) / static_cast<double>(sampleRate) * 1000);
}
std::uint64_t PlayingSound::getRead() const
{
return static_cast<std::uint64_t>((static_cast<double>(readFrames) / static_cast<double>(length)) *
static_cast<double>(getLength()));
}
void PlayingSound::seek(const std::uint64_t &position)
{
auto newPos = static_cast<std::uint64_t>((static_cast<double>(position) / static_cast<double>(getLength())) *
static_cast<double>(length));
seekTo = newPos;
readFrames = newPos;
}
std::shared_ptr<Sound> PlayingSound::getSound() const
{
return sound;
}
std::uint64_t PlayingSound::getId() const
{
return id;
}
bool PlayingSound::isRepeating() const
{
return shouldRepeat;
}
AudioDevice PlayingSound::getPlaybackDevice() const
{
return playbackDevice;
}
void PlayingSound::setVolume(float newVolume)
{
if (device.copy())
{
auto device = this->device.write();
device->get()->masterVolumeFactor = newVolume;
}
}
void PlayingSound::onProgressed(const std::uint64_t &frames)
{
readFrames += frames;
buffer += frames;
if (buffer > (sampleRate / 2))
{
buffer = 0;
Globals::gGui->onSoundProgressed(shared_from_this());
}
}
void PlayingSound::onFinished()
{
if (device.copy() && done)
{
stop();
Globals::gGui->onSoundFinished(shared_from_this());
}
}
void PlayingSound::data_callback(ma_device *rawDevice, void *output, [[maybe_unused]] const void *input,
std::uint32_t frameCount)
{
auto *thiz = reinterpret_cast<PlayingSound *>(rawDevice->pUserData);
if (!thiz->done)
{
auto decoder = thiz->decoder.write();
auto readFrames = ma_decoder_read_pcm_frames(decoder->get(), output, frameCount);
if (thiz->seekTo.load().has_value())
{
ma_decoder_seek_to_pcm_frame(decoder->get(), thiz->seekTo.load().value());
thiz->readFrames = thiz->seekTo.load().value();
thiz->seekTo = std::nullopt;
}
if (thiz->playbackDevice.isDefault && readFrames > 0)
{
thiz->onProgressed(readFrames);
}
if (readFrames <= 0)
{
if (thiz->shouldRepeat)
{
ma_decoder_seek_to_pcm_frame(decoder->get(), 0);
thiz->readFrames = 0;
}
else
{
thiz->done = true;
Globals::gQueue.push_unique(thiz->id, [thiz = thiz->shared_from_this()]() { thiz->onFinished(); });
}
}
}
}
} // namespace Soundux::Objects

View File

@ -0,0 +1,66 @@
#pragma once
#include <atomic>
#include <core/objects/sound.hpp>
#include <helper/audio/device/device.hpp>
#include <lock.hpp>
#include <memory>
#include <optional>
namespace Soundux
{
namespace Objects
{
class PlayingSound : public std::enable_shared_from_this<PlayingSound>
{
sxl::lock<std::shared_ptr<ma_device>> device;
sxl::lock<std::shared_ptr<ma_decoder>> decoder;
private:
AudioDevice playbackDevice;
std::uint64_t length{0};
std::uint64_t readFrames{0};
std::uint32_t sampleRate{0};
std::atomic<bool> done{false};
std::atomic<bool> shouldRepeat{false};
std::atomic<std::optional<std::uint64_t>> seekTo;
std::uint64_t buffer{0};
std::shared_ptr<Sound> sound;
std::uint64_t id;
static std::atomic<std::uint64_t> idCounter;
private:
PlayingSound() = default;
void onFinished();
void onProgressed(const std::uint64_t &frames);
static void data_callback(ma_device *, void *, const void *, std::uint32_t);
public:
void stop();
void pause();
void resume();
//* In Milliseconds
std::uint64_t getRead() const;
std::uint64_t getLength() const;
void repeat(bool);
void setVolume(float);
void seek(const std::uint64_t &);
bool isPaused() const;
bool isRepeating() const;
std::uint64_t getId() const;
AudioDevice getPlaybackDevice() const;
std::shared_ptr<Sound> getSound() const;
public:
~PlayingSound();
static std::shared_ptr<PlayingSound> create(const std::shared_ptr<Sound> &, const AudioDevice &);
};
} // namespace Objects
} // namespace Soundux

View File

@ -29,9 +29,9 @@ namespace Soundux::Objects
return true;
}
std::shared_ptr<IconFetcher> IconFetcher::createInstance()
std::unique_ptr<IconFetcher> IconFetcher::createInstance()
{
auto instance = std::shared_ptr<IconFetcher>(new IconFetcher()); // NOLINT
auto instance = std::unique_ptr<IconFetcher>(new IconFetcher()); // NOLINT
if (instance->setup())
{

View File

@ -22,7 +22,7 @@ namespace Soundux
std::optional<int> getPpid(int pid);
public:
static std::shared_ptr<IconFetcher> createInstance();
static std::unique_ptr<IconFetcher> createInstance();
std::optional<std::string> getIcon(int pid, bool recursive = true);
};
} // namespace Objects

View File

@ -124,24 +124,13 @@ namespace nlohmann
{
static void to_json(json &j, const Soundux::Objects::PlayingSound &obj)
{
j = {
{"sound", obj.sound}, {"id", obj.id},
{"length", obj.length}, {"paused", obj.paused.load()},
{"lengthInMs", obj.lengthInMs}, {"repeat", obj.repeat.load()},
{"readFrames", obj.readFrames}, {"readInMs", obj.readInMs.load()},
};
}
static void from_json(const json &j, Soundux::Objects::PlayingSound &obj)
{
j.at("id").get_to(obj.id);
j.at("sound").get_to(obj.sound);
j.at("length").get_to(obj.length);
j.at("readFrames").get_to(obj.readFrames);
j.at("lengthInMs").get_to(obj.lengthInMs);
j["id"] = obj.getId();
j["sound"] = obj.getSound();
j["paused"] = obj.isPaused();
j["repeat"] = obj.isRepeating();
obj.paused.store(j.at("paused").get<bool>());
obj.repeat.store(j.at("repeat").get<bool>());
obj.readInMs.store(j.at("readInMs").get<std::uint64_t>());
j["lengthInMs"] = obj.getLength();
j["readInMs"] = obj.getRead();
}
};
template <> struct adl_serializer<Soundux::Objects::Settings>

View File

@ -6,10 +6,10 @@
#include <fstream>
#include <optional>
#pragma push_macro("UNICOCDE")
#pragma push_macro("UNICODE")
#undef UNICODE
#include <process.hpp>
#pragma pop_macro("UNICOCDE")
#pragma pop_macro("UNICODE")
#include <regex>
#include <system_error>
@ -101,7 +101,7 @@ namespace Soundux
auto trashFolder = std::filesystem::canonical(home + "/.local/share/Trash");
auto filePath = std::filesystem::canonical(path);
auto trashFileName = filePath.filename().u8string() +
auto trashFileName = filePath.filename().string() +
std::to_string(std::chrono::system_clock::now().time_since_epoch().count());
std::error_code ec;
@ -122,7 +122,7 @@ namespace Soundux
std::ofstream stream(trashFolder / "info" / (trashFileName + ".trashinfo"));
stream << "[Trash Info]" << std::endl
<< "Path=" << filePath.u8string() << std::endl
<< "Path=" << filePath.string() << std::endl
<< "DeletionDate=" << ss.str() << std::endl;
stream.close();
}

View File

@ -2,7 +2,6 @@
#include <atomic>
#include <condition_variable>
#include <functional>
#include <map>
#include <optional>
#include <queue>
#include <thread>

View File

@ -1,4 +1,5 @@
#include "youtube-dl.hpp"
#include "process.hpp"
#include <core/global/globals.hpp>
#include <fancy.hpp>
#include <helper/misc/misc.hpp>
@ -24,17 +25,17 @@ namespace Soundux::Objects
Fancy::fancy.logTime().warning() << "youtube-dl or ffmpeg is not available!" << std::endl;
}
}
std::optional<nlohmann::json> YoutubeDl::getInfo(const std::string &url) const
nlohmann::json YoutubeDl::getInfo(const std::string &url) const
{
if (!isAvailable)
{
return std::nullopt;
return nullptr;
}
if (!std::regex_match(url, urlRegex))
{
Fancy::fancy.logTime().warning() << "Bad url " >> url << std::endl;
return std::nullopt;
return nullptr;
}
auto [result, success] = Helpers::getResultCompact("youtube-dl -i -j \"" + url + "\"");
@ -45,7 +46,7 @@ namespace Soundux::Objects
{
Fancy::fancy.logTime().warning() << "Failed to parse youtube-dl information" << std::endl;
Globals::gGui->onError(Enums::ErrorCode::YtdlInvalidJson);
return std::nullopt;
return nullptr;
}
nlohmann::json j;
@ -67,7 +68,7 @@ namespace Soundux::Objects
Fancy::fancy.logTime().warning() << "Failed to get info from youtube-dl" << std::endl;
Globals::gGui->onError(Enums::ErrorCode::YtdlInformationUnknown);
return std::nullopt;
return nullptr;
}
bool YoutubeDl::download(const std::string &url)
{
@ -99,8 +100,9 @@ namespace Soundux::Objects
currentDownload.reset();
}
currentDownload.emplace("youtube-dl --extract-audio --audio-format mp3 --no-mtime \"" + url + "\" -o \"" +
currentTab->path + "/%(title)s.%(ext)s" + "\"",
currentDownload = std::make_unique<TinyProcessLib::Process>(
"youtube-dl --extract-audio --audio-format mp3 --no-mtime \"" + url + "\" -o \"" + currentTab->path +
"/%(title)s.%(ext)s" + "\"",
"", [](const char *rawData, std::size_t dataLen) {
std::string data(rawData, dataLen);
static const std::regex progressRegex(R"(([0-9.,]+)%.*(ETA (.+)))");

View File

@ -2,10 +2,10 @@
#include <json.hpp>
#include <optional>
#pragma push_macro("UNICOCDE")
#pragma push_macro("UNICODE")
#undef UNICODE
#include <process.hpp>
#pragma pop_macro("UNICOCDE")
#pragma pop_macro("UNICODE")
#include <regex>
#include <string>
@ -18,14 +18,14 @@ namespace Soundux
{
bool isAvailable = false;
static const std::regex urlRegex;
std::optional<TinyProcessLib::Process> currentDownload;
std::unique_ptr<TinyProcessLib::Process> currentDownload;
public:
void setup();
void killDownload();
bool available() const;
bool download(const std::string &);
std::optional<nlohmann::json> getInfo(const std::string &) const;
nlohmann::json getInfo(const std::string &) const;
};
} // namespace Objects
} // namespace Soundux

View File

@ -82,7 +82,6 @@ int main(int argc, char **arguments)
gWinSound = WinSound::createInstance();
#endif
gAudio.setup();
gYtdl.setup();
#if defined(__linux__)
@ -112,7 +111,6 @@ int main(int argc, char **arguments)
gGui->mainLoop();
gAudio.destroy();
#if defined(__linux__)
if (gAudioBackend)
{

@ -1 +1 @@
Subproject commit d43538e165a8f7a927b94718276323fc7b71eab5
Subproject commit 20ce8b89aeb6ffa6dcccdd509d52c0de24f788e6

View File

@ -270,21 +270,6 @@ namespace Soundux::Objects
Webview::Function("startPassthrough", [this](const std::string &app) { return startPassthrough(app); }));
webview->expose(
Webview::Function("stopPassthrough", [this](const std::string &name) { stopPassthrough(name); }));
webview->expose(Webview::Function("unloadSwitchOnConnect", []() {
auto pulseBackend =
std::dynamic_pointer_cast<Soundux::Objects::PulseAudio>(Soundux::Globals::gAudioBackend);
if (pulseBackend)
{
pulseBackend->unloadSwitchOnConnect();
pulseBackend->loadModules();
Globals::gAudio.setup();
}
else
{
Fancy::fancy.logTime().failure()
<< "unloadSwitchOnConnect was called but no pulse backend was detected!" << std::endl;
}
}));
#endif
}
bool WebView::onClose()
@ -308,15 +293,6 @@ namespace Soundux::Objects
static bool once = false;
if (!once)
{
#if defined(__linux__)
if (auto pulseBackend = std::dynamic_pointer_cast<PulseAudio>(Globals::gAudioBackend); pulseBackend)
{
//* We have to call this so that we can trigger an event in the frontend that switchOnConnect was
//* found becausepreviously the UI was not initialized.
pulseBackend->switchOnConnectPresent();
}
#endif
auto future = std::make_shared<std::future<void>>();
*future = std::async(std::launch::async, [future, this] {
translations.settings = webview
@ -397,21 +373,27 @@ namespace Soundux::Objects
webview->callFunction<void>(
Webview::JavaScriptFunction("window.hotkeyReceived", Globals::gHotKeys->getKeySequence(keys), keys));
}
void WebView::onSoundFinished(const PlayingSound &sound)
void WebView::onSoundFinished(const std::shared_ptr<PlayingSound> &sound)
{
Window::onSoundFinished(sound);
if (sound.playbackDevice.isDefault)
if (sound && sound->getPlaybackDevice().isDefault)
{
webview->callFunction<void>(Webview::JavaScriptFunction("window.finishSound", sound));
webview->callFunction<void>(Webview::JavaScriptFunction("window.finishSound", *sound));
}
}
void WebView::onSoundPlayed(const PlayingSound &sound)
void WebView::onSoundPlayed(const std::shared_ptr<PlayingSound> &sound)
{
webview->callFunction<void>(Webview::JavaScriptFunction("window.onSoundPlayed", sound));
if (sound)
{
webview->callFunction<void>(Webview::JavaScriptFunction("window.onSoundPlayed", *sound));
}
void WebView::onSoundProgressed(const PlayingSound &sound)
}
void WebView::onSoundProgressed(const std::shared_ptr<PlayingSound> &sound)
{
webview->callFunction<void>(Webview::JavaScriptFunction("window.updateSound", sound));
if (sound)
{
webview->callFunction<void>(Webview::JavaScriptFunction("window.updateSound", *sound));
}
}
void WebView::onDownloadProgressed(float progress, const std::string &eta)
{
@ -438,11 +420,6 @@ namespace Soundux::Objects
Window::onAllSoundsFinished();
webview->callFunction<void>(Webview::JavaScriptFunction("window.getStore().commit", "clearCurrentlyPlaying"));
}
void WebView::onSwitchOnConnectDetected(bool state)
{
webview->callFunction<void>(
Webview::JavaScriptFunction("window.getStore().commit", "setSwitchOnConnectLoaded", state));
}
void WebView::onAdminRequired()
{
webview->callFunction<void>(

View File

@ -27,18 +27,18 @@ namespace Soundux
void show() override;
void setup() override;
void mainLoop() override;
void onSoundFinished(const PlayingSound &sound) override;
void onHotKeyReceived(const std::vector<Key> &keys) override;
void onAdminRequired() override;
void onSettingsChanged() override;
void onLocalVolumeChanged(int volume) override;
void onRemoteVolumeChanged(int volume) override;
void onSwitchOnConnectDetected(bool state) override;
void onError(const Enums::ErrorCode &error) override;
void onSoundPlayed(const PlayingSound &sound) override;
void onSoundProgressed(const PlayingSound &sound) override;
void onDownloadProgressed(float progress, const std::string &eta) override;
void onSoundPlayed(const std::shared_ptr<PlayingSound> &sound) override;
void onSoundFinished(const std::shared_ptr<PlayingSound> &sound) override;
void onSoundProgressed(const std::shared_ptr<PlayingSound> &sound) override;
};
} // namespace Objects
} // namespace Soundux

View File

@ -22,6 +22,24 @@ namespace Soundux::Objects
tab.sounds = getTabContent(tab);
Globals::gData.setTab(tab.id, tab);
}
setDevices();
}
void Window::setDevices()
{
for (const auto &device : AudioDevice::getDevices())
{
if (device.isDefault)
{
defaultPlayback = device;
}
#if defined(__linux__)
if (device.name == "soundux_sink")
{
remotePlayback = device;
}
#endif
}
}
Window::~Window()
{
@ -204,14 +222,16 @@ namespace Soundux::Objects
return {};
}
#if defined(__linux__)
std::optional<PlayingSound> Window::playSound(const std::uint32_t &id)
std::shared_ptr<PlayingSound> Window::playSound(const std::uint32_t &id)
{
auto sound = Globals::gData.getSound(id);
auto groupedSounds = this->groupedSounds.write();
if (sound)
{
if (!Globals::gSettings.allowOverlapping)
{
stopSounds(true);
stopSounds();
}
if (Globals::gSettings.muteDuringPlayback)
{
@ -228,22 +248,17 @@ namespace Soundux::Objects
Globals::gHotKeys->pressKeys(Globals::gSettings.pushToTalkKeys);
}
auto playingSound = Globals::gAudio.play(*sound);
auto remotePlayingSound = Globals::gAudio.play(*sound, Globals::gAudio.nullSink);
auto playingSound = PlayingSound::create(*sound, defaultPlayback);
auto remotePlayingSound = PlayingSound::create(*sound, remotePlayback);
if (playingSound && remotePlayingSound)
{
groupedSounds->insert({playingSound->id, remotePlayingSound->id});
if (Globals::gSettings.outputs.empty() && playingSound)
{
return *playingSound;
}
if (!Globals::gSettings.outputs.empty() && Globals::gAudioBackend)
{
bool moveSuccess = false;
for (const auto &outputApp : Globals::gSettings.outputs)
{
if (Globals::gAudioBackend->inputSoundTo(Globals::gAudioBackend->getRecordingApp(outputApp)))
if (Globals::gAudioBackend->inputSoundTo(outputApp))
{
moveSuccess = true;
}
@ -252,30 +267,32 @@ namespace Soundux::Objects
if (!moveSuccess)
{
if (playingSound)
stopSound(playingSound->id);
playingSound->stop();
if (remotePlayingSound)
stopSound(remotePlayingSound->id);
remotePlayingSound->stop();
onError(Enums::ErrorCode::FailedToMoveToSink);
return std::nullopt;
}
return *playingSound;
return nullptr;
}
}
}
else
{
Fancy::fancy.logTime().failure() << "Sound " << id << " not found" << std::endl;
onError(Enums::ErrorCode::SoundNotFound);
return std::nullopt;
}
Fancy::fancy.logTime().failure() << "Failed to play sound " << id << std::endl;
onError(Enums::ErrorCode::FailedToPlay);
return std::nullopt;
return nullptr;
}
groupedSounds->insert({playingSound->getId(), {playingSound, remotePlayingSound}});
return playingSound;
}
Fancy::fancy.logTime().failure() << "Sound " << id << " not found" << std::endl;
onError(Enums::ErrorCode::SoundNotFound);
return nullptr;
}
#else
// TODO
std::optional<PlayingSound> Window::playSound(const std::uint32_t &id)
{
auto sound = Globals::gData.getSound(id);
@ -336,138 +353,102 @@ namespace Soundux::Objects
return std::nullopt;
}
#endif
std::optional<PlayingSound> Window::pauseSound(const std::uint32_t &id)
std::shared_ptr<PlayingSound> Window::pauseSound(const std::uint32_t &id)
{
std::optional<std::uint32_t> remoteSoundId;
if (!Globals::gSettings.outputs.empty() && !Globals::gSettings.useAsDefaultDevice)
auto groupedSounds = this->groupedSounds.read();
if (groupedSounds->count(id))
{
auto scoped = groupedSounds.scoped();
if (scoped->find(id) == scoped->end())
auto sounds = groupedSounds->at(id);
if (sounds.first)
{
if (!Globals::gSettings.outputs.empty() || !Globals::gSettings.useAsDefaultDevice)
{
Fancy::fancy.logTime().warning() << "Failed to find remoteSound of sound " << id << std::endl;
sounds.first->pause();
}
}
else
if (sounds.second)
{
remoteSoundId = scoped->at(id);
}
}
auto playingSound = Globals::gAudio.pause(id);
if (remoteSoundId)
{
Globals::gAudio.pause(*remoteSoundId);
sounds.second->pause();
}
if (playingSound)
{
return *playingSound;
return sounds.first;
}
Fancy::fancy.logTime().warning() << "Failed to pause sound " << id << std::endl;
onError(Enums::ErrorCode::FailedToPause);
return std::nullopt;
return nullptr;
}
std::optional<PlayingSound> Window::resumeSound(const std::uint32_t &id)
std::shared_ptr<PlayingSound> Window::resumeSound(const std::uint32_t &id)
{
std::optional<std::uint32_t> remoteSoundId;
if (!Globals::gSettings.outputs.empty() && !Globals::gSettings.useAsDefaultDevice)
auto groupedSounds = this->groupedSounds.read();
if (groupedSounds->count(id))
{
auto scoped = groupedSounds.scoped();
if (scoped->find(id) == scoped->end())
auto sounds = groupedSounds->at(id);
if (sounds.first)
{
if (!Globals::gSettings.outputs.empty() || !Globals::gSettings.useAsDefaultDevice)
{
Fancy::fancy.logTime().warning() << "Failed to find remoteSound of sound " << id << std::endl;
sounds.first->resume();
}
}
else
if (sounds.second)
{
remoteSoundId = scoped->at(id);
}
}
auto playingSound = Globals::gAudio.resume(id);
if (remoteSoundId)
{
Globals::gAudio.resume(*remoteSoundId);
sounds.second->resume();
}
if (playingSound)
{
return *playingSound;
return sounds.first;
}
Fancy::fancy.logTime().warning() << "Failed to resume sound " << id << std::endl;
onError(Enums::ErrorCode::FailedToResume);
return std::nullopt;
return nullptr;
}
std::optional<PlayingSound> Window::seekSound(const std::uint32_t &id, std::uint64_t seekTo)
std::shared_ptr<PlayingSound> Window::seekSound(const std::uint32_t &id, std::uint64_t seekTo)
{
std::optional<std::uint32_t> remoteSoundId;
if (!Globals::gSettings.outputs.empty() && !Globals::gSettings.useAsDefaultDevice)
auto groupedSounds = this->groupedSounds.read();
if (groupedSounds->count(id))
{
auto scoped = groupedSounds.scoped();
if (scoped->find(id) == scoped->end())
auto sounds = groupedSounds->at(id);
if (sounds.first)
{
if (!Globals::gSettings.outputs.empty() || !Globals::gSettings.useAsDefaultDevice)
{
Fancy::fancy.logTime().warning() << "Failed to find remoteSound of sound " << id << std::endl;
sounds.first->seek(seekTo);
}
}
else
if (sounds.second)
{
remoteSoundId = scoped->at(id);
}
}
auto playingSound = Globals::gAudio.seek(id, seekTo);
if (remoteSoundId)
{
Globals::gAudio.seek(*remoteSoundId, seekTo);
sounds.second->seek(seekTo);
}
if (playingSound)
{
return *playingSound;
return sounds.first;
}
Fancy::fancy.logTime().warning() << "Failed to seek sound " << id << " to " << seekTo << std::endl;
onError(Enums::ErrorCode::FailedToSeek);
return std::nullopt;
return nullptr;
}
std::optional<PlayingSound> Window::repeatSound(const std::uint32_t &id, bool shouldRepeat)
std::shared_ptr<PlayingSound> Window::repeatSound(const std::uint32_t &id, bool shouldRepeat)
{
std::optional<std::uint32_t> remoteSoundId;
if (!Globals::gSettings.outputs.empty() && !Globals::gSettings.useAsDefaultDevice)
auto groupedSounds = this->groupedSounds.read();
if (groupedSounds->count(id))
{
auto scoped = groupedSounds.scoped();
if (scoped->find(id) == scoped->end())
auto sounds = groupedSounds->at(id);
if (sounds.first)
{
if (!Globals::gSettings.outputs.empty() || !Globals::gSettings.useAsDefaultDevice)
{
Fancy::fancy.logTime().warning() << "Failed to find remoteSound of sound " << id << std::endl;
sounds.first->repeat(shouldRepeat);
}
}
else
if (sounds.second)
{
remoteSoundId = scoped->at(id);
}
}
auto playingSound = Globals::gAudio.repeat(id, shouldRepeat);
if (remoteSoundId)
{
Globals::gAudio.repeat(*remoteSoundId, shouldRepeat);
sounds.second->repeat(shouldRepeat);
}
if (playingSound)
{
return *playingSound;
return sounds.first;
}
Fancy::fancy.logTime().failure() << "Failed to set repeat-state of sound " << id << " to " << shouldRepeat
<< std::endl;
onError(Enums::ErrorCode::FailedToRepeat);
return std::nullopt;
return nullptr;
}
std::vector<Tab> Window::removeTab(const std::uint32_t &id)
{
@ -476,46 +457,37 @@ namespace Soundux::Objects
}
bool Window::stopSound(const std::uint32_t &id)
{
std::optional<std::uint32_t> remoteSoundId;
if (!Globals::gSettings.outputs.empty() && !Globals::gSettings.useAsDefaultDevice)
auto groupedSounds = this->groupedSounds.write();
if (groupedSounds->count(id))
{
auto scoped = groupedSounds.scoped();
if (scoped->find(id) == scoped->end())
auto sounds = groupedSounds->at(id);
if (sounds.first)
{
Fancy::fancy.logTime().warning() << "Failed to find remoteSound of sound " << id << std::endl;
sounds.first->stop();
}
if (sounds.second)
{
sounds.second->stop();
}
groupedSounds->erase(id);
if (groupedSounds->empty())
{
onAllSoundsFinished();
}
return true;
}
return false;
}
remoteSoundId = scoped->at(id);
}
auto status = Globals::gAudio.stop(id);
if (remoteSoundId)
{
Globals::gAudio.stop(*remoteSoundId);
groupedSounds->erase(id);
}
if (Globals::gAudio.getPlayingSounds().empty())
void Window::stopSounds()
{
groupedSounds.write()->clear();
onAllSoundsFinished();
}
return status;
}
void Window::stopSounds(bool sync)
{
if (!sync)
{
Globals::gQueue.push_unique(0, []() { Globals::gAudio.stopAll(); });
}
else
{
Globals::gAudio.stopAll();
}
onAllSoundsFinished();
groupedSounds->clear();
#if defined(__linux__)
if (Globals::gAudioBackend)
@ -537,13 +509,15 @@ namespace Soundux::Objects
if (sound)
{
sound->get().localVolume = localVolume;
auto groupedSounds = this->groupedSounds.write();
for (auto &playingSound : Globals::gAudio.getPlayingSounds())
for (const auto &entry : *groupedSounds)
{
if (playingSound.sound.id == sound->get().id && playingSound.playbackDevice.isDefault)
const auto &local = entry.second.first;
if (local->getSound().id == sound->get().id)
{
playingSound.raw.device.load()->masterVolumeFactor =
static_cast<float>(localVolume ? *localVolume : Globals::gSettings.localVolume) / 100.f;
local->setVolume(static_cast<float>(localVolume ? *localVolume : Globals::gSettings.localVolume) /
100.f);
}
}
@ -558,16 +532,19 @@ namespace Soundux::Objects
std::optional<Sound> Window::setCustomRemoteVolume(const std::uint32_t &id, const std::optional<int> &remoteVolume)
{
auto sound = Globals::gData.getSound(id);
auto groupedSounds = this->groupedSounds.write();
if (sound)
{
sound->get().remoteVolume = remoteVolume;
for (auto &playingSound : Globals::gAudio.getPlayingSounds())
for (const auto &entry : *groupedSounds)
{
if (playingSound.sound.id == sound->get().id && !playingSound.playbackDevice.isDefault)
const auto &remote = entry.second.second;
if (remote && remote->getSound().id == sound->get().id && !remote->getPlaybackDevice().isDefault)
{
playingSound.raw.device.load()->masterVolumeFactor =
static_cast<float>(remoteVolume ? *remoteVolume : Globals::gSettings.remoteVolume) / 100.f;
remote->setVolume(
static_cast<float>(remoteVolume ? *remoteVolume : Globals::gSettings.remoteVolume) / 100.f);
}
}
@ -581,21 +558,25 @@ namespace Soundux::Objects
}
void Window::onVolumeChanged()
{
for (const auto &playingSound : Globals::gAudio.getPlayingSounds())
auto groupedSounds = this->groupedSounds.write();
for (const auto &entry : *groupedSounds)
{
int newVolume = 0;
const auto &sound = playingSound.sound;
const auto &[local, remote] = entry.second;
const auto &sound = local->getSound();
if (playingSound.playbackDevice.isDefault)
if (local)
{
newVolume = sound.localVolume ? *sound.localVolume : Globals::gSettings.localVolume;
}
else
{
newVolume = sound.remoteVolume ? *sound.remoteVolume : Globals::gSettings.remoteVolume;
local->setVolume(
static_cast<float>(sound.localVolume ? *sound.localVolume : Globals::gSettings.localVolume) /
100.f);
}
playingSound.raw.device.load()->masterVolumeFactor = static_cast<float>(newVolume) / 100.f;
if (remote)
{
remote->setVolume(
static_cast<float>(sound.remoteVolume ? *sound.remoteVolume : Globals::gSettings.remoteVolume) /
100.f);
}
}
}
Settings Window::changeSettings(Settings settings)
@ -611,19 +592,19 @@ namespace Soundux::Objects
#if defined(__linux__)
if (settings.audioBackend != oldSettings.audioBackend)
{
stopSounds(true);
stopSounds();
if (Globals::gAudioBackend)
{
Globals::gAudioBackend->destroy();
}
Globals::gAudioBackend = AudioBackend::createInstance(settings.audioBackend);
Globals::gAudio.setup();
setDevices();
}
if (Globals::gAudioBackend)
{
if (!Globals::gAudio.getPlayingSounds().empty())
auto groupedSounds = this->groupedSounds.read();
if (!groupedSounds->empty())
{
if (settings.muteDuringPlayback && !oldSettings.muteDuringPlayback)
{
@ -677,9 +658,10 @@ namespace Soundux::Objects
for (const auto &outputApp : settings.outputs)
{
if (!settings.outputs.empty() && !Globals::gAudio.getPlayingSounds().empty())
if (!settings.outputs.empty() && !groupedSounds->empty())
{
if (!Globals::gAudioBackend->inputSoundTo(Globals::gAudioBackend->getRecordingApp(outputApp)))
if (!Globals::gAudioBackend->inputSoundTo(outputApp))
{
onError(Enums::ErrorCode::FailedToMoveToSink);
}
@ -818,7 +800,7 @@ namespace Soundux::Objects
else if (auto pipeWireApp = std::dynamic_pointer_cast<PipeWireRecordingApp>(stream);
pipeWireApp)
{
auto icon = Soundux::Globals::gIcons->getIcon(static_cast<int>(pipeWireApp->pid));
auto icon = Soundux::Globals::gIcons->getIcon(static_cast<int>(pipeWireApp->node.pid));
if (icon)
{
iconStream->appIcon = *icon;
@ -866,7 +848,7 @@ namespace Soundux::Objects
}
if (auto pipeWireApp = std::dynamic_pointer_cast<PipeWirePlaybackApp>(stream); pipeWireApp)
{
auto icon = Soundux::Globals::gIcons->getIcon(static_cast<int>(pipeWireApp->pid));
auto icon = Soundux::Globals::gIcons->getIcon(static_cast<int>(pipeWireApp->node.pid));
if (icon)
{
iconStream->appIcon = *icon;
@ -888,7 +870,7 @@ namespace Soundux::Objects
{
for (const auto &outputApp : Globals::gSettings.outputs)
{
if (!Globals::gAudioBackend->inputSoundTo(Globals::gAudioBackend->getRecordingApp(outputApp)))
if (!Globals::gAudioBackend->inputSoundTo(outputApp))
{
onError(Enums::ErrorCode::FailedToMoveToSink);
success = false;
@ -897,7 +879,7 @@ namespace Soundux::Objects
if (success)
{
if (!Globals::gAudioBackend->passthroughFrom(Globals::gAudioBackend->getPlaybackApp(name)))
if (!Globals::gAudioBackend->passthroughFrom(name))
{
success = false;
}
@ -915,19 +897,19 @@ namespace Soundux::Objects
{
if (Globals::gAudioBackend)
{
if (Globals::gAudio.getPlayingSounds().empty() &&
Globals::gAudioBackend->currentlyPassedThrough().size() == 1)
if (!Globals::gAudioBackend->stopPassthrough(name))
{
onError(Enums::ErrorCode::FailedToMoveBackPassthrough);
}
auto groupedSounds = this->groupedSounds.read();
if (groupedSounds->empty() && Globals::gAudioBackend->currentlyPassedThrough().empty())
{
if (!Globals::gAudioBackend->stopSoundInput())
{
onError(Enums::ErrorCode::FailedToMoveBack);
}
}
if (!Globals::gAudioBackend->stopPassthrough(name))
{
onError(Enums::ErrorCode::FailedToMoveBackPassthrough);
}
}
}
#else
@ -936,16 +918,15 @@ namespace Soundux::Objects
return Globals::gAudio.getAudioDevices();
}
#endif
void Window::onSoundFinished(const PlayingSound &sound)
void Window::onSoundFinished(const std::shared_ptr<PlayingSound> &sound)
{
auto scoped = groupedSounds.scoped();
if (scoped->find(sound.id) != scoped->end())
auto groupedSounds = this->groupedSounds.write();
if (groupedSounds->count(sound->getId()))
{
scoped->erase(sound.id);
groupedSounds->erase(sound->getId());
}
scoped.unlock();
if (Globals::gAudio.getPlayingSounds().size() == 1)
if (groupedSounds->empty())
{
onAllSoundsFinished();
}
@ -988,7 +969,7 @@ namespace Soundux::Objects
}
#endif
}
void Window::onSoundPlayed([[maybe_unused]] const PlayingSound &sound)
void Window::onSoundPlayed([[maybe_unused]] const std::shared_ptr<PlayingSound> &sound)
{
if (!Globals::gSettings.pushToTalkKeys.empty())
{
@ -1019,25 +1000,37 @@ namespace Soundux::Objects
bool Window::toggleSoundPlayback()
{
bool shouldPause = true;
for (const auto &sound : Globals::gAudio.getPlayingSounds())
auto groupedSounds = this->groupedSounds.read();
for (const auto &sounds : *groupedSounds)
{
if (sound.paused)
const auto &[local, remote] = sounds.second;
if (local->isPaused())
{
shouldPause = false;
break;
}
}
auto soundsCopy = groupedSounds.copy();
for (const auto &[local, remote] : soundsCopy)
for (const auto &sounds : *groupedSounds)
{
const auto &[local, remote] = sounds.second;
if (shouldPause)
{
pauseSound(local);
local->pause();
if (remote)
{
remote->pause();
}
}
else
{
resumeSound(local);
local->resume();
if (remote)
{
remote->resume();
}
}
}

View File

@ -1,14 +1,12 @@
#pragma once
#include <core/objects/settings.hpp>
#include <helper/audio/audio.hpp>
#include <helper/audio/sound/sound.hpp>
#if defined(__linux__)
#include <helper/audio/linux/backend.hpp>
#endif
#include <atomic>
#include <cstdint>
#include <queue>
#include <lock.hpp>
#include <map>
#include <string>
#include <var_guard.hpp>
namespace Soundux
{
@ -34,7 +32,10 @@ namespace Soundux
friend class Hotkeys;
protected:
sxl::var_guard<std::map<std::uint32_t, std::uint32_t>> groupedSounds;
using SoundPair = std::pair<std::shared_ptr<PlayingSound>, std::shared_ptr<PlayingSound>>;
sxl::lock<std::map<std::uint32_t, SoundPair>> groupedSounds;
AudioDevice defaultPlayback;
AudioDevice remotePlayback;
struct
{
@ -77,17 +78,18 @@ namespace Soundux
virtual std::optional<Tab> setSortMode(const std::uint32_t &, Enums::SortMode);
protected:
void setDevices();
virtual void stopSounds();
virtual void onVolumeChanged();
virtual bool toggleSoundPlayback();
virtual void stopSounds(bool = false);
virtual bool stopSound(const std::uint32_t &);
protected:
virtual std::optional<PlayingSound> playSound(const std::uint32_t &);
virtual std::optional<PlayingSound> pauseSound(const std::uint32_t &);
virtual std::optional<PlayingSound> resumeSound(const std::uint32_t &);
virtual std::optional<PlayingSound> repeatSound(const std::uint32_t &, bool);
virtual std::optional<PlayingSound> seekSound(const std::uint32_t &, std::uint64_t);
virtual std::shared_ptr<PlayingSound> playSound(const std::uint32_t &);
virtual std::shared_ptr<PlayingSound> pauseSound(const std::uint32_t &);
virtual std::shared_ptr<PlayingSound> resumeSound(const std::uint32_t &);
virtual std::shared_ptr<PlayingSound> repeatSound(const std::uint32_t &, bool);
virtual std::shared_ptr<PlayingSound> seekSound(const std::uint32_t &, std::uint64_t);
virtual std::optional<Sound> setHotkey(const std::uint32_t &, const std::vector<Key> &);
virtual std::optional<Sound> setCustomLocalVolume(const std::uint32_t &, const std::optional<int> &);
@ -103,13 +105,13 @@ namespace Soundux
virtual void onSettingsChanged() = 0;
virtual void onLocalVolumeChanged(int) = 0;
virtual void onRemoteVolumeChanged(int) = 0;
virtual void onSwitchOnConnectDetected(bool) = 0;
virtual void onSoundPlayed(const PlayingSound &);
virtual void onError(const Enums::ErrorCode &) = 0;
virtual void onSoundFinished(const PlayingSound &);
virtual void onHotKeyReceived(const std::vector<Key> &);
virtual void onSoundProgressed(const PlayingSound &) = 0;
virtual void onDownloadProgressed(float, const std::string &) = 0;
virtual void onSoundPlayed(const std::shared_ptr<PlayingSound> &);
virtual void onSoundFinished(const std::shared_ptr<PlayingSound> &);
virtual void onSoundProgressed(const std::shared_ptr<PlayingSound> &) = 0;
};
} // namespace Objects
} // namespace Soundux