nf7/file/audio_context.cc

232 lines
6.3 KiB
C++

#include <atomic>
#include <memory>
#include <imgui.h>
#include <miniaudio.h>
#include "nf7.hh"
#include "common/audio_queue.hh"
#include "common/dir_item.hh"
#include "common/generic_context.hh"
#include "common/generic_type_info.hh"
#include "common/ptr_selector.hh"
#include "common/thread.hh"
namespace nf7 {
namespace {
class AudioContext final : public nf7::File, public nf7::DirItem {
public:
static inline const nf7::GenericTypeInfo<AudioContext> kType = {
"Audio/Context", {"nf7::DirItem",}};
static void UpdateTypeTooltip() noexcept {
ImGui::TextUnformatted("Drives miniaudio context.");
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::audio::Queue");
ImGui::Bullet(); ImGui::TextUnformatted(
"there's no merit to use multiple contexts");
ImGui::Bullet(); ImGui::TextUnformatted(
"the context remains alive after file deletion until unused");
}
class Queue;
AudioContext(nf7::Env&) noexcept;
AudioContext(nf7::Deserializer& ar) noexcept : AudioContext(ar.env()) {
}
void Serialize(nf7::Serializer&) const noexcept override {
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<AudioContext>(env);
}
void Update() noexcept override;
void UpdateMenu() noexcept override;
void UpdateTooltip() noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::DirItem, nf7::audio::Queue>(t).Select(this, q_.get());
}
private:
std::shared_ptr<Queue> q_;
const char* popup_ = nullptr;
// for device list popup
struct DeviceList {
std::atomic<bool> working;
bool success;
ma_device_info* play;
ma_uint32 play_n;
ma_device_info* cap;
ma_uint32 cap_n;
};
std::shared_ptr<DeviceList> devlist_;
void UpdateDeviceList(const ma_device_info*, size_t n) noexcept;
};
class AudioContext::Queue final : public nf7::audio::Queue,
public std::enable_shared_from_this<AudioContext::Queue> {
public:
struct Runner final {
Runner(std::weak_ptr<Queue> owner) noexcept : owner_(owner) {
}
void operator()(Task&& t) {
if (auto k = owner_.lock()) {
t(k->ctx_.get());
}
}
private:
std::weak_ptr<Queue> owner_;
};
using Thread = nf7::Thread<Runner, Task>;
enum State {
kInitializing,
kReady,
kBroken,
};
static std::shared_ptr<Queue> Create(AudioContext& f) noexcept {
auto ret = std::make_shared<Queue>(f);
ret->th_ = std::make_shared<Thread>(f, Runner {ret});
ret->Push(
std::make_shared<nf7::GenericContext>(f.env(), 0, "creating ma_context"),
[ret](auto) {
auto ctx = std::make_shared<ma_context>();
if (MA_SUCCESS == ma_context_init(nullptr, 0, nullptr, ctx.get())) {
ret->ctx_ = std::move(ctx);
ret->state_ = kReady;
} else {
ret->state_ = kBroken;
}
});
return ret;
}
Queue() = delete;
Queue(AudioContext& f) noexcept : env_(&f.env()) {
}
~Queue() noexcept {
th_->Push(
std::make_shared<nf7::GenericContext>(*env_, 0, "deleting ma_context"),
[ctx = std::move(ctx_)](auto) { if (ctx) ma_context_uninit(ctx.get()); }
);
}
Queue(const Queue&) = delete;
Queue(Queue&&) = delete;
Queue& operator=(const Queue&) = delete;
Queue& operator=(Queue&&) = delete;
void Push(const std::shared_ptr<nf7::Context>& ctx, Task&& task) noexcept override {
th_->Push(ctx, std::move(task));
}
std::shared_ptr<audio::Queue> self() noexcept override { return shared_from_this(); }
State state() const noexcept { return state_; }
size_t tasksDone() const noexcept { return th_->tasksDone(); }
private:
Env* const env_;
std::shared_ptr<Thread> th_;
std::atomic<State> state_ = kInitializing;
std::shared_ptr<ma_context> ctx_;
};
AudioContext::AudioContext(Env& env) noexcept :
File(kType, env), DirItem(DirItem::kMenu | DirItem::kTooltip),
q_(AudioContext::Queue::Create(*this)) {
}
void AudioContext::Update() noexcept {
if (auto popup = std::exchange(popup_, nullptr)) {
ImGui::OpenPopup(popup);
}
if (ImGui::BeginPopup("DeviceList")) {
auto& p = devlist_;
ImGui::TextUnformatted("Audio/Context: device list");
if (ImGui::IsWindowAppearing()) {
if (!p) {
p = std::make_shared<DeviceList>();
}
p->working = true;
q_->Push(
std::make_shared<nf7::GenericContext>(*this, "fetching audio device list"),
[p](auto ctx) {
p->success = false;
if (ctx) {
const auto ret = ma_context_get_devices(
ctx, &p->play, &p->play_n, &p->cap, &p->cap_n);
p->success = ret == MA_SUCCESS;
}
p->working = false;
});
}
ImGui::Indent();
if (p->working) {
ImGui::TextUnformatted("fetching audio devices... :)");
} else {
if (p->success) {
ImGui::TextUnformatted("playback:");
ImGui::Indent();
UpdateDeviceList(p->play, p->play_n);
ImGui::Unindent();
ImGui::TextUnformatted("capture:");
ImGui::Indent();
UpdateDeviceList(p->cap, p->cap_n);
ImGui::Unindent();
} else {
ImGui::TextUnformatted("failed to fetch devices X(");
}
}
ImGui::Unindent();
ImGui::EndPopup();
}
}
void AudioContext::UpdateMenu() noexcept {
if (ImGui::MenuItem("display available devices")) {
popup_ = "DeviceList";
}
}
void AudioContext::UpdateTooltip() noexcept {
const auto state = q_->state();
const char* state_str =
state == Queue::kInitializing? "initializing":
state == Queue::kReady ? "ready":
state == Queue::kBroken ? "broken": "unknown";
ImGui::Text("state: %s", state_str);
}
void AudioContext::UpdateDeviceList(const ma_device_info* p, size_t n) noexcept {
for (size_t i = 0; i < n; ++i) {
const auto& info = p[i];
const auto name = std::to_string(i) + ": " + info.name;
ImGui::Selectable(name.c_str(), false, ImGuiSelectableFlags_DontClosePopups);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text("index : %zu", i);
ImGui::Text("name : %s", info.name);
ImGui::Text("default : %s", info.isDefault? "true": "false");
ImGui::EndTooltip();
}
}
}
}
} // namespace nf7