Add ZSound MIME type and other updates
This commit is contained in:
parent
f5dbdf96d5
commit
f9b5ce4577
18 changed files with 363 additions and 93 deletions
|
@ -119,6 +119,7 @@ if(DEFINED ANDROID_NDK)
|
||||||
set(USE_SPEEX OFF CACHE BOOL "" FORCE)
|
set(USE_SPEEX OFF CACHE BOOL "" FORCE)
|
||||||
set(USE_G719 OFF CACHE BOOL "" FORCE)
|
set(USE_G719 OFF CACHE BOOL "" FORCE)
|
||||||
set(USE_VORBIS OFF CACHE BOOL "" FORCE)
|
set(USE_VORBIS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(BUILD_FMT ON CACHE BOOL "" FORCE)
|
||||||
endif()
|
endif()
|
||||||
if (BUILD_SDL)
|
if (BUILD_SDL)
|
||||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
|
|
5
add-mime-types.sh
Executable file
5
add-mime-types.sh
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
OLDDIR="$(pwd)"
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
xdg-mime install assets/zsm-mime.xml
|
||||||
|
cd "$OLDDIR"
|
|
@ -7,7 +7,7 @@ GenericName=Looping audio player
|
||||||
Exec=looper -n %f
|
Exec=looper -n %f
|
||||||
Icon=looper
|
Icon=looper
|
||||||
StartupWMClass=looper;com.complecwaft.Looper;com.complecwaft.Looper.GTK
|
StartupWMClass=looper;com.complecwaft.Looper;com.complecwaft.Looper.GTK
|
||||||
MimeType=audio/x-wav;audio/ogg;audio/x-vorbis+ogg;audio/x-opus+ogg;audio/mpeg;audio/flac;audio/xm;audio/x-mod;
|
MimeType=audio/x-wav;audio/ogg;audio/x-vorbis+ogg;audio/x-opus+ogg;audio/mpeg;audio/flac;audio/xm;audio/x-mod;audio/x-zsound;
|
||||||
Categories=Audio;AudioVideo;
|
Categories=Audio;AudioVideo;
|
||||||
Terminal=false
|
Terminal=false
|
||||||
SingleMainWindow=true
|
SingleMainWindow=true
|
||||||
|
|
11
assets/zsm-mime.xml
Normal file
11
assets/zsm-mime.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>
|
||||||
|
<mime-type type="audio/x-zsound">
|
||||||
|
<comment>ZSound file for the Commander X16</comment>
|
||||||
|
<glob pattern="*.zsm"/>
|
||||||
|
<magic priority="50">
|
||||||
|
<match type="string" value="zm" offset="0" />
|
||||||
|
</magic>
|
||||||
|
</mime-type>
|
||||||
|
</mime-info>
|
||||||
|
|
10
backends/playback/zsm/properties.inc
Normal file
10
backends/playback/zsm/properties.inc
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#define BOOL_PROPERTY(name, default_value) _PROPERTY(name, bool, default_value)
|
||||||
|
#define DOUBLE_PROPERTY(name, default_value) _PROPERTY(name, double, default_value)
|
||||||
|
BOOL_PROPERTY(pcm_enable, true)
|
||||||
|
BOOL_PROPERTY(psg_enable, true)
|
||||||
|
BOOL_PROPERTY(fm_enable, true)
|
||||||
|
DOUBLE_PROPERTY(pcm_volume, 1.0)
|
||||||
|
DOUBLE_PROPERTY(psg_volume, 1.0)
|
||||||
|
DOUBLE_PROPERTY(fm_volume, 1.0)
|
||||||
|
#undef BOOL_PROPERTY
|
||||||
|
#undef DOUBLE_PROPERTY
|
|
@ -17,17 +17,26 @@ extern "C" {
|
||||||
#define HZ (AUDIO_SAMPLERATE)
|
#define HZ (AUDIO_SAMPLERATE)
|
||||||
#define BUFFERS 32
|
#define BUFFERS 32
|
||||||
#define _PROPERTY(name, type, default_value) \
|
#define _PROPERTY(name, type, default_value) \
|
||||||
bool ZsmBackend::name() { \
|
type ZsmBackend::name() { \
|
||||||
std::optional<google::protobuf::Any> value_maybe = get(#name); \
|
std::optional<google::protobuf::Any> value_maybe = get(#name); \
|
||||||
if (value_maybe.has_value()) { \
|
if (value_maybe.has_value()) { \
|
||||||
return resolve_##type(value_maybe.value()); \
|
return resolve_value<type>(value_maybe.value()); \
|
||||||
} \
|
} \
|
||||||
return default_value; \
|
return default_value; \
|
||||||
}
|
}
|
||||||
#define BOOL_PROPERTY(name, default_value) _PROPERTY(name, bool, default_value)
|
|
||||||
BOOL_PROPERTY(pcm_enable, true)
|
#include "properties.inc"
|
||||||
BOOL_PROPERTY(psg_enable, true)
|
#undef _PROPERTY
|
||||||
BOOL_PROPERTY(fm_enable, true)
|
std::vector<Property> ZsmBackend::get_property_list() {
|
||||||
|
std::vector<Property> properties;
|
||||||
|
properties.push_back(make_property(PropertyType::Boolean, "Enable PCM channel", "pcm_enable"));
|
||||||
|
properties.push_back(make_property(PropertyType::Boolean, "Enable PSG channels", "psg_enable"));
|
||||||
|
properties.push_back(make_property(PropertyType::Boolean, "Enable FM channels", "fm_enable"));
|
||||||
|
properties.push_back(make_property(PropertyType::Double, "Volume of PCM channel", "pcm_volume", make_hint(0.0, 1.0)));
|
||||||
|
properties.push_back(make_property(PropertyType::Double, "Volume of PSG channels", "psg_volume", make_hint(0.0, 1.0)));
|
||||||
|
properties.push_back(make_property(PropertyType::Double, "Volume of FM channels", "fm_volume", make_hint(0.0, 1.0)));
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
void ZsmBackend::load(const char *filename) {
|
void ZsmBackend::load(const char *filename) {
|
||||||
memset(&spec, 0, sizeof(spec));
|
memset(&spec, 0, sizeof(spec));
|
||||||
spec.format = AUDIO_S16SYS;
|
spec.format = AUDIO_S16SYS;
|
||||||
|
@ -129,6 +138,22 @@ void ZsmBackend::load(const char *filename) {
|
||||||
loop_start = this->loop_pos;
|
loop_start = this->loop_pos;
|
||||||
fm_stream = SDL_NewAudioStream(AUDIO_S16SYS, 2, YM_FREQ, AUDIO_S16SYS, 2, PSG_FREQ);
|
fm_stream = SDL_NewAudioStream(AUDIO_S16SYS, 2, YM_FREQ, AUDIO_S16SYS, 2, PSG_FREQ);
|
||||||
DEBUG.writefln("fm_stream: %ld -> %ld", YM_FREQ, PSG_FREQ);
|
DEBUG.writefln("fm_stream: %ld -> %ld", YM_FREQ, PSG_FREQ);
|
||||||
|
#define _PROPERTY(name, type, default_value) \
|
||||||
|
{ \
|
||||||
|
std::string type_str = #type; \
|
||||||
|
google::protobuf::Any value; \
|
||||||
|
if (type_str == "bool") { \
|
||||||
|
BooleanProperty value_b; \
|
||||||
|
value_b.set_value(default_value); \
|
||||||
|
value.PackFrom(value_b); \
|
||||||
|
} else if (type_str == "double") { \
|
||||||
|
DoubleProperty value_d; \
|
||||||
|
value_d.set_value(default_value); \
|
||||||
|
value.PackFrom(value_d); \
|
||||||
|
} \
|
||||||
|
property_defaults[#name] = value; \
|
||||||
|
}
|
||||||
|
#include "properties.inc"
|
||||||
}
|
}
|
||||||
extern SDL_AudioSpec obtained;
|
extern SDL_AudioSpec obtained;
|
||||||
void ZsmBackend::switch_stream(int idx) {
|
void ZsmBackend::switch_stream(int idx) {
|
||||||
|
@ -399,3 +424,61 @@ double ZsmBackend::get_position() {
|
||||||
int ZsmBackend::get_stream_idx() {
|
int ZsmBackend::get_stream_idx() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ZsmBackend::audio_step(size_t samples) {
|
||||||
|
if (samples == 0) return;
|
||||||
|
while (remain != 0 && pcm_fifo_avail() < samples) {
|
||||||
|
if (pcm_read_rate() == 0) break;
|
||||||
|
if ((--remain) == 0) {
|
||||||
|
if (islooped) {
|
||||||
|
cur = loop;
|
||||||
|
remain = loop_rem;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size_t oldpos = file->get_pos();
|
||||||
|
uint8_t sample = audio_sample[cur++];
|
||||||
|
pcm_write_fifo(sample);
|
||||||
|
}
|
||||||
|
samples *= 2;
|
||||||
|
int16_t *psg_ptr = psg_buf.get_item_sized<int16_t>(samples);
|
||||||
|
int16_t *pcm_ptr = pcm_buf.get_item_sized<int16_t>(samples);
|
||||||
|
psg_render(psg_ptr, samples / 2);
|
||||||
|
pcm_render(pcm_ptr, samples / 2);
|
||||||
|
int16_t *out_ptr = out_buf.get_item_sized<int16_t>(samples);
|
||||||
|
// The exact amount of samples needed for the stream.
|
||||||
|
double ratio = ((double)YM_FREQ) / ((double)PSG_FREQ);
|
||||||
|
size_t needed_samples = ((size_t)std::floor(samples * ratio)) / 2;
|
||||||
|
int16_t *ym_ptr = ym_buf.get_item_sized<int16_t>(needed_samples * 2);
|
||||||
|
YM_stream_update(ym_ptr, needed_samples);
|
||||||
|
assert(SDL_AudioStreamPut(fm_stream, ym_ptr, needed_samples * 2 * sizeof(int16_t)) == 0);
|
||||||
|
while (SDL_AudioStreamAvailable(fm_stream) < ((samples + 2) * sizeof(int16_t))) {
|
||||||
|
YM_stream_update(ym_ptr, 1);
|
||||||
|
assert(SDL_AudioStreamPut(fm_stream, ym_ptr, 2 * sizeof(int16_t)) == 0);
|
||||||
|
}
|
||||||
|
int16_t *ym_resample_ptr = ym_resample_buf.get_item_sized<int16_t>(samples);
|
||||||
|
ssize_t ym_resample_len = SDL_AudioStreamGet(fm_stream, ym_resample_ptr, (samples + 2) * sizeof(int16_t));
|
||||||
|
assert(ym_resample_len >= 0);
|
||||||
|
ym_resample_len /= sizeof(int16_t);
|
||||||
|
for (size_t i = 0; i < samples / 2; i++) {
|
||||||
|
size_t j = i * 2;
|
||||||
|
int16_t psg[2] = {(int16_t)(psg_ptr[j] >> 1), (int16_t)(psg_ptr[j + 1] >> 1)};
|
||||||
|
int16_t pcm[2] = {(int16_t)(pcm_ptr[j] >> 2), (int16_t)(pcm_ptr[j + 1] >> 2)};
|
||||||
|
if (!pcm_enable()) memset(pcm, 0, sizeof(pcm));
|
||||||
|
if (!psg_enable()) memset(psg, 0, sizeof(psg));
|
||||||
|
pcm[0] *= pcm_volume();
|
||||||
|
pcm[1] *= pcm_volume();
|
||||||
|
psg[0] *= psg_volume();
|
||||||
|
psg[1] *= psg_volume();
|
||||||
|
int16_t vera[2] = {(int16_t)(psg[0] + pcm[0]), (int16_t)(psg[1] + pcm[1])};
|
||||||
|
int16_t fm[2] = {ym_resample_ptr[j], ym_resample_ptr[j + 1]};
|
||||||
|
if (!fm_enable()) memset(fm, 0, sizeof(fm));
|
||||||
|
fm[0] *= fm_volume();
|
||||||
|
fm[1] *= fm_volume();
|
||||||
|
int16_t mix[2] = {(int16_t)(vera[0] + (fm[0] >> 1)), (int16_t)(vera[1] + (fm[1] >> 1))};
|
||||||
|
out_ptr[j++] = mix[0];
|
||||||
|
out_ptr[j++] = mix[1];
|
||||||
|
}
|
||||||
|
audio_buf.push(out_ptr, samples);
|
||||||
|
}
|
||||||
|
|
|
@ -96,60 +96,10 @@ class ZsmBackend : public PlaybackBackend {
|
||||||
bool pcm_enable();
|
bool pcm_enable();
|
||||||
bool psg_enable();
|
bool psg_enable();
|
||||||
bool fm_enable();
|
bool fm_enable();
|
||||||
int16_t combine_audio(int16_t a, int16_t b) {
|
double pcm_volume();
|
||||||
return (int16_t)((((int32_t)a) + ((int32_t)b)) >> 1);
|
double psg_volume();
|
||||||
}
|
double fm_volume();
|
||||||
void audio_step(size_t samples) {
|
void audio_step(size_t samples);
|
||||||
if (samples == 0) return;
|
|
||||||
while (remain != 0 && pcm_fifo_avail() < samples) {
|
|
||||||
if (pcm_read_rate() == 0) break;
|
|
||||||
if ((--remain) == 0) {
|
|
||||||
if (islooped) {
|
|
||||||
cur = loop;
|
|
||||||
remain = loop_rem;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
size_t oldpos = file->get_pos();
|
|
||||||
uint8_t sample = audio_sample[cur++];
|
|
||||||
pcm_write_fifo(sample);
|
|
||||||
}
|
|
||||||
samples *= 2;
|
|
||||||
int16_t *psg_ptr = psg_buf.get_item_sized<int16_t>(samples);
|
|
||||||
int16_t *pcm_ptr = pcm_buf.get_item_sized<int16_t>(samples);
|
|
||||||
psg_render(psg_ptr, samples / 2);
|
|
||||||
pcm_render(pcm_ptr, samples / 2);
|
|
||||||
int16_t *out_ptr = out_buf.get_item_sized<int16_t>(samples);
|
|
||||||
// The exact amount of samples needed for the stream.
|
|
||||||
double ratio = ((double)YM_FREQ) / ((double)PSG_FREQ);
|
|
||||||
size_t needed_samples = ((size_t)std::floor(samples * ratio)) / 2;
|
|
||||||
int16_t *ym_ptr = ym_buf.get_item_sized<int16_t>(needed_samples * 2);
|
|
||||||
YM_stream_update(ym_ptr, needed_samples);
|
|
||||||
assert(SDL_AudioStreamPut(fm_stream, ym_ptr, needed_samples * 2 * sizeof(int16_t)) == 0);
|
|
||||||
while (SDL_AudioStreamAvailable(fm_stream) < ((samples + 2) * sizeof(int16_t))) {
|
|
||||||
YM_stream_update(ym_ptr, 1);
|
|
||||||
assert(SDL_AudioStreamPut(fm_stream, ym_ptr, 2 * sizeof(int16_t)) == 0);
|
|
||||||
}
|
|
||||||
int16_t *ym_resample_ptr = ym_resample_buf.get_item_sized<int16_t>(samples);
|
|
||||||
ssize_t ym_resample_len = SDL_AudioStreamGet(fm_stream, ym_resample_ptr, (samples + 2) * sizeof(int16_t));
|
|
||||||
assert(ym_resample_len >= 0);
|
|
||||||
ym_resample_len /= sizeof(int16_t);
|
|
||||||
for (size_t i = 0; i < samples / 2; i++) {
|
|
||||||
size_t j = i * 2;
|
|
||||||
int16_t psg[2] = {psg_ptr[j] >> 1, psg_ptr[j + 1] >> 1};
|
|
||||||
int16_t pcm[2] = {pcm_ptr[j] >> 1, pcm_ptr[j + 1] >> 1};
|
|
||||||
if (!pcm_enable()) memset(pcm, 0, sizeof(pcm));
|
|
||||||
if (!psg_enable()) memset(psg, 0, sizeof(psg));
|
|
||||||
int16_t vera[2] = {psg[0] + pcm[0], psg[1] + pcm[1]};
|
|
||||||
int16_t fm[2] = {ym_resample_ptr[j], ym_resample_ptr[j + 1]};
|
|
||||||
if (!fm_enable()) memset(fm, 0, sizeof(fm));
|
|
||||||
int16_t mix[2] = {vera[0] + (fm[0] >> 1), vera[1] + (fm[1] >> 1)};
|
|
||||||
out_ptr[j++] = mix[0];
|
|
||||||
out_ptr[j++] = mix[1];
|
|
||||||
}
|
|
||||||
audio_buf.push(out_ptr, samples);
|
|
||||||
}
|
|
||||||
inline void *reserve(size_t len) {
|
inline void *reserve(size_t len) {
|
||||||
return (void*)audio_buf.reserve(len);
|
return (void*)audio_buf.reserve(len);
|
||||||
}
|
}
|
||||||
|
@ -183,6 +133,7 @@ class ZsmBackend : public PlaybackBackend {
|
||||||
inline std::string get_name() override {
|
inline std::string get_name() override {
|
||||||
return "ZSM player";
|
return "ZSM player";
|
||||||
}
|
}
|
||||||
|
std::vector<Property> get_property_list() override;
|
||||||
void seek(double position) override;
|
void seek(double position) override;
|
||||||
void load(const char *filename) override;
|
void load(const char *filename) override;
|
||||||
void switch_stream(int idx) override;
|
void switch_stream(int idx) override;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <assets/assets.h>
|
#include <assets/assets.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <options.hpp>
|
#include <options.hpp>
|
||||||
|
#include "imgui/imgui.h"
|
||||||
#include "ui_backend.hpp"
|
#include "ui_backend.hpp"
|
||||||
#include "thirdparty/CLI11.hpp"
|
#include "thirdparty/CLI11.hpp"
|
||||||
#include <web_functions.hpp>
|
#include <web_functions.hpp>
|
||||||
|
@ -141,6 +142,9 @@ void MainLoop::FileLoaded() {
|
||||||
SetWindowTitle("Looper");
|
SetWindowTitle("Looper");
|
||||||
}
|
}
|
||||||
streams = playback->get_streams();
|
streams = playback->get_streams();
|
||||||
|
properties = playback->get_property_list();
|
||||||
|
boolean_properties.clear();
|
||||||
|
double_properties.clear();
|
||||||
}
|
}
|
||||||
void MainLoop::GuiFunction() {
|
void MainLoop::GuiFunction() {
|
||||||
#if defined(__EMSCRIPTEN__)||defined(__ANDROID__)
|
#if defined(__EMSCRIPTEN__)||defined(__ANDROID__)
|
||||||
|
@ -195,10 +199,10 @@ void MainLoop::GuiFunction() {
|
||||||
show_demo_window = !show_demo_window;
|
show_demo_window = !show_demo_window;
|
||||||
set_option<double>("ui.imgui.demo_window", show_demo_window);
|
set_option<double>("ui.imgui.demo_window", show_demo_window);
|
||||||
}
|
}
|
||||||
ImGui::EndMenu();
|
|
||||||
if (ImGui::MenuItem(_TR_CTX("Main menu | Debug", "Edit properties"), nullptr, property_editor)) {
|
if (ImGui::MenuItem(_TR_CTX("Main menu | Debug", "Edit properties"), nullptr, property_editor)) {
|
||||||
property_editor = !property_editor;
|
property_editor = !property_editor;
|
||||||
}
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
if (ImGui::BeginMenu(_TRI_CTX(ICON_FK_INFO_CIRCLE, "Main menu", "Help"))) {
|
if (ImGui::BeginMenu(_TRI_CTX(ICON_FK_INFO_CIRCLE, "Main menu", "Help"))) {
|
||||||
if (ImGui::MenuItem(_TRI_CTX(ICON_FK_INFO, "Main menu | Help", "About"), nullptr, about_window)) {
|
if (ImGui::MenuItem(_TRI_CTX(ICON_FK_INFO, "Main menu | Help", "About"), nullptr, about_window)) {
|
||||||
|
@ -304,17 +308,88 @@ void MainLoop::GuiFunction() {
|
||||||
ImGui::SetNextWindowDockID(dockid);
|
ImGui::SetNextWindowDockID(dockid);
|
||||||
ImGui::Begin("Property Editor", &property_editor);
|
ImGui::Begin("Property Editor", &property_editor);
|
||||||
{
|
{
|
||||||
static std::string property_name;
|
for (auto property : properties) {
|
||||||
static bool property_value;
|
ImGui::PushID(property.path().c_str());
|
||||||
ImGui::InputText("Property Name", &property_name);
|
bool valid = false;
|
||||||
ImGui::Checkbox("Enabled", &property_value);
|
switch (property.type()) {
|
||||||
if (ImGui::Button("Set")) {
|
case PropertyType::Double: {
|
||||||
BooleanProperty property;
|
std::optional<double> min;
|
||||||
property.set_value(property_value);
|
std::optional<double> max;
|
||||||
google::protobuf::Any value;
|
double value = 0.0;
|
||||||
value.PackFrom(property);
|
if (double_properties.contains(property.path())) {
|
||||||
playback->set_property(property_name, value);
|
value = double_properties[property.path()];
|
||||||
}
|
} else {
|
||||||
|
auto value_to_resolve = playback->get_property(property.path());
|
||||||
|
if (value_to_resolve.has_value()) {
|
||||||
|
value = resolve_value<double>(value_to_resolve.value());
|
||||||
|
}
|
||||||
|
double_properties[property.path()] = value;
|
||||||
|
}
|
||||||
|
if (property.has_hint() && property.hint().has_range()) {
|
||||||
|
auto range = property.hint().range();
|
||||||
|
if (range.has_min() && range.has_max()) {
|
||||||
|
float flt = (float)value;
|
||||||
|
if (ImGui::SliderFloat(property.path().c_str(), &flt, (float)range.min(), (float)range.max())) {
|
||||||
|
double_properties[property.path()] = flt;
|
||||||
|
}
|
||||||
|
valid = true;
|
||||||
|
} else {
|
||||||
|
if (range.has_min()) min = range.min();
|
||||||
|
if (range.has_max()) max = range.max();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!valid) {
|
||||||
|
ImGui::InputDouble(property.path().c_str(), &value);
|
||||||
|
if (min.has_value() && value < min) {
|
||||||
|
value = min.value();
|
||||||
|
}
|
||||||
|
if (max.has_value() && value > max) {
|
||||||
|
value = max.value();
|
||||||
|
}
|
||||||
|
double_properties[property.path()] = value;
|
||||||
|
valid = true;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case PropertyType::Boolean: {
|
||||||
|
bool value = false;
|
||||||
|
if (boolean_properties.contains(property.path())) {
|
||||||
|
value = boolean_properties[property.path()];
|
||||||
|
} else {
|
||||||
|
auto value_to_resolve = playback->get_property(property.path());
|
||||||
|
if (value_to_resolve.has_value()) {
|
||||||
|
value = resolve_value<bool>(value_to_resolve.value());
|
||||||
|
}
|
||||||
|
boolean_properties[property.path()] = value;
|
||||||
|
}
|
||||||
|
if (ImGui::Checkbox(property.path().c_str(), &value)) {
|
||||||
|
boolean_properties[property.path()] = value;
|
||||||
|
}
|
||||||
|
valid = true;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
if (valid) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Set")) {
|
||||||
|
switch (property.type()) {
|
||||||
|
case PropertyType::Double: {
|
||||||
|
DoubleProperty property_d;
|
||||||
|
property_d.set_value(double_properties[property.path()]);
|
||||||
|
google::protobuf::Any value;
|
||||||
|
value.PackFrom(property_d);
|
||||||
|
playback->set_property(property.path(), value);
|
||||||
|
} break;
|
||||||
|
case PropertyType::Boolean: {
|
||||||
|
BooleanProperty property_b;
|
||||||
|
property_b.set_value(boolean_properties[property.path()]);
|
||||||
|
google::protobuf::Any value;
|
||||||
|
value.PackFrom(property_b);
|
||||||
|
playback->set_property(property.path(), value);
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <map>
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <SDL_image.h>
|
#include <SDL_image.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
@ -53,6 +53,9 @@ class MainLoop : public RendererBackend {
|
||||||
UIBackend *cur_backend;
|
UIBackend *cur_backend;
|
||||||
friend class ImGuiUIBackend;
|
friend class ImGuiUIBackend;
|
||||||
std::atomic_bool exit_flag;
|
std::atomic_bool exit_flag;
|
||||||
|
std::map<std::string, bool> boolean_properties;
|
||||||
|
std::map<std::string, double> double_properties;
|
||||||
|
std::vector<Property> properties;
|
||||||
std::vector<PlaybackStream> streams;
|
std::vector<PlaybackStream> streams;
|
||||||
public:
|
public:
|
||||||
Playback *playback;
|
Playback *playback;
|
||||||
|
|
|
@ -27,6 +27,36 @@ message BooleanProperty {
|
||||||
message BytesProperty {
|
message BytesProperty {
|
||||||
bytes value = 1;
|
bytes value = 1;
|
||||||
};
|
};
|
||||||
|
message PropertyHintRange {
|
||||||
|
optional double min = 1;
|
||||||
|
optional double max = 2;
|
||||||
|
};
|
||||||
|
message PropertyHintEnum {
|
||||||
|
repeated string data = 1;
|
||||||
|
};
|
||||||
|
message PropertyHint {
|
||||||
|
oneof hint {
|
||||||
|
PropertyHintRange range = 1;
|
||||||
|
fixed64 max_length = 2;
|
||||||
|
PropertyHintEnum enum = 3;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
enum PropertyType {
|
||||||
|
Double = 0;
|
||||||
|
Boolean = 1;
|
||||||
|
String = 2;
|
||||||
|
Bytes = 3;
|
||||||
|
Stream = 4;
|
||||||
|
AudioSpec = 5;
|
||||||
|
StreamID = 6;
|
||||||
|
}
|
||||||
|
message Property {
|
||||||
|
PropertyType type = 1;
|
||||||
|
optional PropertyId id = 5;
|
||||||
|
optional string path = 2;
|
||||||
|
optional string name = 4;
|
||||||
|
optional PropertyHint hint = 3;
|
||||||
|
};
|
||||||
message StreamId {
|
message StreamId {
|
||||||
uint64 id = 504;
|
uint64 id = 504;
|
||||||
};
|
};
|
||||||
|
@ -38,9 +68,6 @@ message Stream {
|
||||||
message StreamList {
|
message StreamList {
|
||||||
repeated Stream streams = 500;
|
repeated Stream streams = 500;
|
||||||
}
|
}
|
||||||
message Position {
|
|
||||||
double pos = 508;
|
|
||||||
};
|
|
||||||
enum EndianID {
|
enum EndianID {
|
||||||
LITTLE = 0;
|
LITTLE = 0;
|
||||||
BIG = 1;
|
BIG = 1;
|
||||||
|
|
|
@ -31,6 +31,9 @@ message SimpleAckResponse {
|
||||||
message InitCommand {
|
message InitCommand {
|
||||||
string filename = 1;
|
string filename = 1;
|
||||||
uint64 idx = 2;
|
uint64 idx = 2;
|
||||||
|
};
|
||||||
|
message GetPropertyListCommand {
|
||||||
|
|
||||||
};
|
};
|
||||||
message RPCCall {
|
message RPCCall {
|
||||||
uint64 cmdid = 1;
|
uint64 cmdid = 1;
|
||||||
|
@ -41,8 +44,12 @@ message RPCCall {
|
||||||
ResetProperty reset = 5;
|
ResetProperty reset = 5;
|
||||||
QuitCmd quit = 6;
|
QuitCmd quit = 6;
|
||||||
InitCommand init = 7;
|
InitCommand init = 7;
|
||||||
|
GetPropertyListCommand get_property_list = 8;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
message PropertyList {
|
||||||
|
repeated Property list = 1;
|
||||||
|
}
|
||||||
message RPCResponse {
|
message RPCResponse {
|
||||||
uint64 cmdid = 1;
|
uint64 cmdid = 1;
|
||||||
oneof response {
|
oneof response {
|
||||||
|
@ -51,5 +58,6 @@ message RPCResponse {
|
||||||
PropertyData data = 4;
|
PropertyData data = 4;
|
||||||
ResetResponse reset = 5;
|
ResetResponse reset = 5;
|
||||||
ErrorResponse err = 6;
|
ErrorResponse err = 6;
|
||||||
|
PropertyList property_list = 7;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -684,3 +684,7 @@ std::optional<google::protobuf::Any> PlaybackInstance::get_property(std::string
|
||||||
std::optional<google::protobuf::Any> PlaybackInstance::reset_property(std::string path) {
|
std::optional<google::protobuf::Any> PlaybackInstance::reset_property(std::string path) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
std::vector<Property> PlaybackInstance::get_property_list() {
|
||||||
|
if (process == nullptr) return {};
|
||||||
|
return process->get_property_list();
|
||||||
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ extern "C" {
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "playback_process.hpp"
|
#include "playback_process.hpp"
|
||||||
#include <google/protobuf/any.h>
|
#include <google/protobuf/any.h>
|
||||||
|
#include <ipc/internal.pb.h>
|
||||||
using namespace soundtouch;
|
using namespace soundtouch;
|
||||||
using std::span;
|
using std::span;
|
||||||
using std::optional;
|
using std::optional;
|
||||||
|
@ -220,6 +221,9 @@ class Playback {
|
||||||
LoopFunction();
|
LoopFunction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
virtual std::vector<Property> get_property_list() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
static Playback *Create(bool *daemon_found, bool daemon = false);
|
static Playback *Create(bool *daemon_found, bool daemon = false);
|
||||||
};
|
};
|
||||||
|
@ -333,6 +337,7 @@ public:
|
||||||
void set_property(std::string path, google::protobuf::Any) override;
|
void set_property(std::string path, google::protobuf::Any) override;
|
||||||
std::optional<google::protobuf::Any> get_property(std::string path) override;
|
std::optional<google::protobuf::Any> get_property(std::string path) override;
|
||||||
std::optional<google::protobuf::Any> reset_property(std::string path) override;
|
std::optional<google::protobuf::Any> reset_property(std::string path) override;
|
||||||
|
std::vector<Property> get_property_list() override;
|
||||||
float volume;
|
float volume;
|
||||||
float speed;
|
float speed;
|
||||||
float tempo;
|
float tempo;
|
||||||
|
|
|
@ -15,16 +15,6 @@ struct PlaybackStream {
|
||||||
int id;
|
int id;
|
||||||
};
|
};
|
||||||
class PlaybackBackendHelper;
|
class PlaybackBackendHelper;
|
||||||
enum PropertyType {
|
|
||||||
Double,
|
|
||||||
String,
|
|
||||||
Bytes,
|
|
||||||
Boolean
|
|
||||||
};
|
|
||||||
struct Property {
|
|
||||||
PropertyType type;
|
|
||||||
std::string path;
|
|
||||||
};
|
|
||||||
class PlaybackBackend {
|
class PlaybackBackend {
|
||||||
double prevRate;
|
double prevRate;
|
||||||
size_t minSamples;
|
size_t minSamples;
|
||||||
|
@ -87,6 +77,9 @@ class PlaybackBackend {
|
||||||
inline double get_rate() {
|
inline double get_rate() {
|
||||||
return this->rate;
|
return this->rate;
|
||||||
}
|
}
|
||||||
|
inline virtual std::vector<Property> get_property_list() {
|
||||||
|
return std::vector<Property>();
|
||||||
|
}
|
||||||
inline virtual bool set(std::string path, google::protobuf::Any value) {properties[path] = value; return true;}
|
inline virtual bool set(std::string path, google::protobuf::Any value) {properties[path] = value; return true;}
|
||||||
inline virtual std::optional<google::protobuf::Any> get(std::string path) {return properties.contains(path) ? properties[path] : std::optional<google::protobuf::Any>();}
|
inline virtual std::optional<google::protobuf::Any> get(std::string path) {return properties.contains(path) ? properties[path] : std::optional<google::protobuf::Any>();}
|
||||||
inline virtual std::optional<google::protobuf::Any> reset(std::string path) {
|
inline virtual std::optional<google::protobuf::Any> reset(std::string path) {
|
||||||
|
|
|
@ -296,6 +296,17 @@ MaybeError PlaybackProcessServiceImpl::Quit(const QuitCmd *request) {
|
||||||
cur_backend_lock.get_unsafe()->cleanup();
|
cur_backend_lock.get_unsafe()->cleanup();
|
||||||
return MaybeError();
|
return MaybeError();
|
||||||
}
|
}
|
||||||
|
PropertyList PlaybackProcessServiceImpl::GetPropertyList(const GetPropertyListCommand *cmd) {
|
||||||
|
std::vector<Property> list;
|
||||||
|
{
|
||||||
|
list = cur_backend_lock.lock()->get_property_list();
|
||||||
|
}
|
||||||
|
PropertyList output;
|
||||||
|
for (auto el : list) {
|
||||||
|
output.add_list()->CopyFrom(el);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
MaybeError PlaybackProcessServiceImpl::Init(const InitCommand *cmd) {
|
MaybeError PlaybackProcessServiceImpl::Init(const InitCommand *cmd) {
|
||||||
MaybeError ret;
|
MaybeError ret;
|
||||||
MaybeError *response = &ret;
|
MaybeError *response = &ret;
|
||||||
|
@ -485,6 +496,9 @@ RPCResponse PlaybackProcess::handle_command(RPCCall &call) {
|
||||||
} else {
|
} else {
|
||||||
resp.mutable_render()->CopyFrom(output.output());
|
resp.mutable_render()->CopyFrom(output.output());
|
||||||
}
|
}
|
||||||
|
} else if (call.has_get_property_list()) {
|
||||||
|
PropertyList output = impl.GetPropertyList(&call.get_property_list());
|
||||||
|
resp.mutable_property_list()->CopyFrom(output);
|
||||||
}
|
}
|
||||||
if (!resp.has_ack()) {
|
if (!resp.has_ack()) {
|
||||||
delete ack;
|
delete ack;
|
||||||
|
@ -701,3 +715,18 @@ double PlaybackProcess::get_cached_rate() {
|
||||||
void PlaybackProcess::set_rate(double value) {
|
void PlaybackProcess::set_rate(double value) {
|
||||||
set_property_double(PropertyId::PlaybackRate, value);
|
set_property_double(PropertyId::PlaybackRate, value);
|
||||||
}
|
}
|
||||||
|
std::vector<Property> PlaybackProcess::get_property_list() {
|
||||||
|
RPCCall call;
|
||||||
|
GetPropertyListCommand cmd;
|
||||||
|
call.mutable_get_property_list()->CopyFrom(cmd);
|
||||||
|
RPCResponse resp = SendCommand(&call);
|
||||||
|
if (resp.has_property_list()) {
|
||||||
|
std::vector<Property> output;
|
||||||
|
for (size_t i = 0; i < resp.property_list().list_size(); i++) {
|
||||||
|
output.push_back(resp.property_list().list(i));
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
} else {
|
||||||
|
return std::vector<Property>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ class PlaybackProcessServiceImpl {
|
||||||
ResetResponse Reset(const ResetProperty *request);
|
ResetResponse Reset(const ResetProperty *request);
|
||||||
MaybeError Quit(const QuitCmd *request);
|
MaybeError Quit(const QuitCmd *request);
|
||||||
MaybeError Init(const InitCommand *cmd);
|
MaybeError Init(const InitCommand *cmd);
|
||||||
|
PropertyList GetPropertyList(const GetPropertyListCommand *cmd);
|
||||||
};
|
};
|
||||||
//#define DEBUG_PRINT_IPC
|
//#define DEBUG_PRINT_IPC
|
||||||
void print_ipc_message(const google::protobuf::Message &msg, size_t level = 0);
|
void print_ipc_message(const google::protobuf::Message &msg, size_t level = 0);
|
||||||
|
@ -118,6 +119,7 @@ class PlaybackProcess {
|
||||||
|
|
||||||
std::optional<google::protobuf::Any> get_property(std::string path);
|
std::optional<google::protobuf::Any> get_property(std::string path);
|
||||||
bool set_property(std::string path, google::protobuf::Any value);
|
bool set_property(std::string path, google::protobuf::Any value);
|
||||||
|
std::vector<Property> get_property_list();
|
||||||
PlaybackProcess(std::string filename, int idx = 0);
|
PlaybackProcess(std::string filename, int idx = 0);
|
||||||
~PlaybackProcess();
|
~PlaybackProcess();
|
||||||
};
|
};
|
||||||
|
|
23
util.cpp
23
util.cpp
|
@ -180,3 +180,26 @@ int NotSDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_d
|
||||||
SDL_FreeAudioStream(stream);
|
SDL_FreeAudioStream(stream);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
PropertyHint make_hint(double min, double max) {
|
||||||
|
PropertyHint output;
|
||||||
|
auto range = output.mutable_range();
|
||||||
|
range->set_min(min);
|
||||||
|
range->set_max(max);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
Property make_property(PropertyType type, std::string name, PropertyId id, std::optional<std::string> path, std::optional<PropertyHint> hint) {
|
||||||
|
Property output;
|
||||||
|
output.set_id(id);
|
||||||
|
output.set_name(name);
|
||||||
|
output.set_type(type);
|
||||||
|
if (path.has_value()) output.set_path(path.value());
|
||||||
|
if (hint.has_value()) output.mutable_hint()->CopyFrom(hint.value());
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
Property make_property(PropertyType type, std::string name, std::string path, std::optional<PropertyHint> hint) {
|
||||||
|
return make_property(type, name, PropertyId::BackendSpecific, path, hint)
|
||||||
|
}
|
||||||
|
Property make_property(PropertyType type, std::string name, PropertyId id, std::optional<PropertyHint> hint) {
|
||||||
|
return make_property(type, name, id, {}, hint)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
50
util.hpp
50
util.hpp
|
@ -334,6 +334,9 @@ class DynPtr {
|
||||||
DynPtr(size_t len) : DynPtr() {
|
DynPtr(size_t len) : DynPtr() {
|
||||||
resize(len);
|
resize(len);
|
||||||
}
|
}
|
||||||
|
DynPtr(std::string input) : DynPtr(input.length()) {
|
||||||
|
memcpy(this->ptr, input.data(), input.length());
|
||||||
|
}
|
||||||
/// @brief Gets the pointer, but does not reallocate it.
|
/// @brief Gets the pointer, but does not reallocate it.
|
||||||
/// @param T The type of pointer to return. May be void to return void*.
|
/// @param T The type of pointer to return. May be void to return void*.
|
||||||
/// @returns The pointer, owned by the DynPtr object.
|
/// @returns The pointer, owned by the DynPtr object.
|
||||||
|
@ -493,28 +496,65 @@ inline T *resolve_any(google::protobuf::Any value) {
|
||||||
value.UnpackTo(output);
|
value.UnpackTo(output);
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
inline bool resolve_bool(google::protobuf::Any value) {
|
template<class T>
|
||||||
|
inline T resolve_value(google::protobuf::Any value) {
|
||||||
|
throw std::exception();
|
||||||
|
}
|
||||||
|
template<>
|
||||||
|
inline bool resolve_value<bool>(google::protobuf::Any value) {
|
||||||
BooleanProperty *property = resolve_any<BooleanProperty>(value);
|
BooleanProperty *property = resolve_any<BooleanProperty>(value);
|
||||||
bool output = property->value();
|
bool output = property->value();
|
||||||
delete property;
|
delete property;
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
inline std::string resolve_string(google::protobuf::Any value) {
|
template<>
|
||||||
|
inline std::string resolve_value<std::string>(google::protobuf::Any value) {
|
||||||
StringProperty *property = resolve_any<StringProperty>(value);
|
StringProperty *property = resolve_any<StringProperty>(value);
|
||||||
std::string output = property->value();
|
std::string output = property->value();
|
||||||
delete property;
|
delete property;
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
inline std::string resolve_bytes(google::protobuf::Any value) {
|
template<>
|
||||||
|
inline DynPtr resolve_value<DynPtr>(google::protobuf::Any value) {
|
||||||
BytesProperty *property = resolve_any<BytesProperty>(value);
|
BytesProperty *property = resolve_any<BytesProperty>(value);
|
||||||
std::string output = property->value();
|
std::string output = property->value();
|
||||||
delete property;
|
delete property;
|
||||||
return output;
|
return DynPtr(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline double resolve_double(google::protobuf::Any value) {
|
template<>
|
||||||
|
inline double resolve_value(google::protobuf::Any value) {
|
||||||
DoubleProperty *property = resolve_any<DoubleProperty>(value);
|
DoubleProperty *property = resolve_any<DoubleProperty>(value);
|
||||||
double output = property->value();
|
double output = property->value();
|
||||||
delete property;
|
delete property;
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
template<class T>
|
||||||
|
inline google::protobuf::Any value_to_any(T value) {
|
||||||
|
google::protobuf::Any output;
|
||||||
|
output.PackFrom(value);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
template<>
|
||||||
|
inline google::protobuf::Any value_to_any<double>(double value) {
|
||||||
|
DoubleProperty output;
|
||||||
|
output.set_value(value);
|
||||||
|
return value_to_any<DoubleProperty>(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline google::protobuf::Any value_to_any<bool>(bool value) {
|
||||||
|
BooleanProperty output;
|
||||||
|
output.set_value(value);
|
||||||
|
return value_to_any<BooleanProperty>(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline google::protobuf::Any value_to_any<std::string>(std::string value) {
|
||||||
|
StringProperty output;
|
||||||
|
output.set_value(value);
|
||||||
|
return value_to_any<StringProperty>(output);
|
||||||
|
}
|
||||||
|
Property make_property(PropertyType type, std::string name, PropertyId id, std::optional<PropertyHint> hint);
|
||||||
|
Property make_property(PropertyType type, std::string name, std::string path, std::optional<PropertyHint> hint = {});
|
||||||
|
Property make_property(PropertyType type, std::string name, PropertyId id = PropertyId::BackendSpecific, std::optional<std::string> path = {}, std::optional<PropertyHint> hint = {});
|
||||||
|
|
Loading…
Reference in a new issue