Added the ability to get/set equalizer settings via IEnvironment, and

added related messages to the WebSocketServer.

Also implemented sending/updating eq settings in the Android client data
layer, and stubbed a screen that can be used for display eq values.
This commit is contained in:
casey langen 2018-12-27 23:22:23 -08:00
parent 39b068fc52
commit 497b35a008
24 changed files with 310 additions and 26 deletions

View File

@ -104,7 +104,7 @@ namespace musik { namespace core {
functionName,
[&plugins](
musik::core::sdk::IPlugin* unused,
std::shared_ptr<T> plugin,
std::shared_ptr<T> plugin,
const std::string& fn)
{
plugins.push_back(plugin);
@ -134,6 +134,20 @@ namespace musik { namespace core {
}
}
std::shared_ptr<musik::core::sdk::IPlugin> QueryGuid(const std::string& guid) {
using T = musik::core::sdk::IPlugin;
std::shared_ptr<T> result;
using Deleter = PluginFactory::ReleaseDeleter<T>;
Instance().QueryInterface<T, Deleter>(
"GetPlugin",
[&result, guid](T* unused, std::shared_ptr<T> plugin, const std::string& fn) {
if (std::string(plugin->Guid()) == guid) {
result = plugin;
}
});
return result;
}
private:
struct Descriptor {
musik::core::sdk::IPlugin* plugin;

View File

@ -66,6 +66,8 @@ typedef void(*SetDebug)(IDebug*);
typedef void(*SetSimpleDataProvider)(ISimpleDataProvider*);
typedef void(*SetIndexerNotifier)(IIndexerNotifier*);
static const std::string SUPEREQ_PLUGIN_GUID = "6f0ed53b-0f13-4220-9b0a-ca496b6421cc";
static IMessageQueue* messageQueue = nullptr;
static ILibraryPtr library;
static IPlaybackService* playback = nullptr;
@ -82,6 +84,17 @@ static void saveEnvironment() {
}
}
static void getEqualizerPluginAndPrefs(
std::shared_ptr<IPlugin>& plugin,
std::shared_ptr<Preferences>& prefs)
{
plugin = PluginFactory::Instance().QueryGuid(SUPEREQ_PLUGIN_GUID);
if (plugin) {
prefs = Preferences::ForPlugin(plugin->Name());
}
}
static class Debug: public IDebug {
public:
virtual void Verbose(const char* tag, const char* message) override {
@ -249,6 +262,71 @@ static class Environment: public IEnvironment {
}
}
virtual bool GetEqualizerBandValues(double target[], size_t count) override {
if (count != EqualizerBandCount) {
return false;
}
std::shared_ptr<IPlugin> plugin;
std::shared_ptr<Preferences> prefs;
getEqualizerPluginAndPrefs(plugin, prefs);
if (plugin && prefs) {
for (size_t i = 0; i < EqualizerBandCount; i++) {
target[i] = prefs->GetDouble(std::to_string(EqualizerBands[i]), 0.0);
}
return true;
}
return false;
}
virtual bool SetEqualizerBandValues(double values[], size_t count) override {
if (count != EqualizerBandCount) {
return false;
}
std::shared_ptr<IPlugin> plugin;
std::shared_ptr<Preferences> prefs;
getEqualizerPluginAndPrefs(plugin, prefs);
if (plugin && prefs) {
for (size_t i = 0; i < EqualizerBandCount; i++) {
prefs->SetDouble(std::to_string(EqualizerBands[i]), values[i]);
}
plugin->Reload();
return true;
}
return false;
}
virtual bool GetEqualizerEnabled() override {
std::shared_ptr<IPlugin> plugin;
std::shared_ptr<Preferences> prefs;
getEqualizerPluginAndPrefs(plugin, prefs);
if (plugin && prefs) {
return prefs->GetBool("enabled", false);
}
return false;
}
virtual void SetEqualizerEnabled(bool enabled) {
std::shared_ptr<IPlugin> plugin;
std::shared_ptr<Preferences> prefs;
getEqualizerPluginAndPrefs(plugin, prefs);
if (plugin && prefs) {
if (prefs->GetBool("enabled", false) != enabled) {
prefs->SetBool("enabled", enabled);
plugin->Reload();
}
}
}
virtual void ReloadPlaybackOutput() override {
if (playback) {
playback->ReloadOutput();

View File

@ -61,6 +61,10 @@ namespace musik { namespace core { namespace sdk {
virtual void SetReplayGainMode(ReplayGainMode mode) = 0;
virtual float GetPreampGain() = 0;
virtual void SetPreampGain(float gain) = 0;
virtual bool GetEqualizerEnabled() = 0;
virtual void SetEqualizerEnabled(bool enabled) = 0;
virtual bool GetEqualizerBandValues(double target[], size_t count) = 0;
virtual bool SetEqualizerBandValues(double values[], size_t count) = 0;
virtual void ReloadPlaybackOutput() = 0;
virtual void SetDefaultOutput(IOutput* output) = 0;
virtual IOutput* GetDefaultOutput() = 0;

View File

@ -35,6 +35,7 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
namespace musik {
namespace core {
@ -115,6 +116,13 @@ namespace musik {
Crossfade = 1
};
static const size_t EqualizerBandCount = 18;
static const size_t EqualizerBands[] = {
65, 92, 131, 185, 262, 370, 523, 740, 1047, 1480,
2093, 2960, 4186, 5920, 8372, 11840, 16744, 22000
};
namespace category {
static const char* Album = "album";
static const char* Artist = "artist";

View File

@ -150,16 +150,7 @@ void EqualizerOverlay::ShowOverlay() {
}
std::shared_ptr<IPlugin> EqualizerOverlay::FindPlugin() {
std::shared_ptr<IPlugin> result;
using Deleter = PluginFactory::ReleaseDeleter<IPlugin>;
PluginFactory::Instance().QueryInterface<IPlugin, Deleter>(
"GetPlugin",
[&result](IPlugin* unused, std::shared_ptr<IPlugin> plugin, const std::string& fn) {
if (std::string(plugin->Guid()) == SUPEREQ_PLUGIN_GUID) {
result = plugin;
}
});
return result;
return PluginFactory::Instance().QueryGuid(SUPEREQ_PLUGIN_GUID);
}
void EqualizerOverlay::Layout() {

View File

@ -251,16 +251,7 @@ void ServerOverlay::Show(Callback callback) {
}
std::shared_ptr<IPlugin> ServerOverlay::FindServerPlugin() {
std::shared_ptr<IPlugin> result;
using Deleter = PluginFactory::ReleaseDeleter<IPlugin>;
PluginFactory::Instance().QueryInterface<IPlugin, Deleter>(
"GetPlugin",
[&result](IPlugin* unused, std::shared_ptr<IPlugin> plugin, const std::string& fn) {
if (std::string(plugin->Guid()) == WEBSOCKET_PLUGIN_GUID) {
result = plugin;
}
});
return result;
return PluginFactory::Instance().QueryGuid(WEBSOCKET_PLUGIN_GUID);
}
void ServerOverlay::Load() {

View File

@ -59,6 +59,10 @@
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize" />
<activity android:name=".ui.settings.activity.RemoteEqActivity"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize" />
<activity
android:name="io.casey.musikcube.remote.ui.category.activity.CategoryBrowseActivity"
android:screenOrientation="portrait"

View File

@ -37,6 +37,8 @@ class Messages {
SetDefaultOutputDriver("set_default_output_driver"),
GetGainSettings("get_gain_settings"),
SetGainSettings("set_gain_settings"),
GetEqualizerSettings("get_equalizer_settings"),
SetEqualizerSettings("set_equalizer_settings"),
RunIndexer("run_indexer"),
GetTransportType("get_transport_type"),
SetTransportType("set_transport_type"),
@ -112,6 +114,8 @@ class Messages {
const val DEVICE_ID = "device_id"
const val REPLAYGAIN_MODE = "replaygain_mode"
const val PREAMP_GAIN = "preamp_gain"
const val ENABLED = "enabled"
const val BANDS = "bands"
}
}

View File

@ -59,6 +59,9 @@ interface IDataProvider {
fun getGainSettings(): Observable<IGainSettings>
fun updateGainSettings(replayGainMode: ReplayGainMode, preampGain: Float): Observable<Boolean>
fun getEqualizerSettings(): Observable<IEqualizerSettings>
fun updateEqualizerSettings(enabled: Boolean, freqs: Array<Double>): Observable<Boolean>
fun reindexMetadata(): Observable<Boolean>
fun rebuildMetadata(): Observable<Boolean>

View File

@ -0,0 +1,6 @@
package io.casey.musikcube.remote.service.websocket.model
interface IEqualizerSettings {
val enabled: Boolean
val bands: Map<Int, Double>
}

View File

@ -1,7 +1,6 @@
package io.casey.musikcube.remote.service.websocket.model
interface IGainSettings {
val replayGainMode: ReplayGainMode
val preampGain: Float
}

View File

@ -489,6 +489,30 @@ class RemoteDataProvider(private val service: WebSocketService) : IDataProvider
.observeOn(AndroidSchedulers.mainThread())
}
override fun getEqualizerSettings(): Observable<IEqualizerSettings> {
val message = SocketMessage.Builder
.request(Messages.Request.GetEqualizerSettings)
.build()
return service.observe(message, client)
.flatMap<IEqualizerSettings> { socketMessage ->
Observable.just(RemoteEqualizerSettings(socketMessage.getJsonObject()))
}
.observeOn(AndroidSchedulers.mainThread())
}
override fun updateEqualizerSettings(enabled: Boolean, freqs: Array<Double>): Observable<Boolean> {
val message = SocketMessage.Builder
.request(Messages.Request.SetEqualizerSettings)
.addOption(Messages.Key.ENABLED, enabled)
.addOption(Messages.Key.BANDS, freqs.toDoubleArray())
.build()
return service.observe(message, client)
.flatMap<Boolean> { socketMessage -> isSuccessful(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
override fun reindexMetadata(): Observable<Boolean> {
return runIndexer(Messages.Value.REINDEX)
}

View File

@ -0,0 +1,31 @@
package io.casey.musikcube.remote.service.websocket.model.impl.remote
import io.casey.musikcube.remote.service.websocket.Messages
import io.casey.musikcube.remote.service.websocket.model.IEqualizerSettings
import org.json.JSONObject
import java.lang.NumberFormatException
class RemoteEqualizerSettings(private val json: JSONObject): IEqualizerSettings {
private val bandToFreqMap: Map<Int, Double>
init {
bandToFreqMap = mutableMapOf()
json.optJSONObject(Messages.Key.BANDS)?.let { bands ->
bands.keys().forEach { freq ->
try {
bandToFreqMap[freq.toInt()] = bands.getDouble(freq)
}
catch (ex: NumberFormatException) {
/* ugh... */
}
}
}
}
override val enabled: Boolean
get() = json.optBoolean(Messages.Key.ENABLED, false)
override val bands: Map<Int, Double>
get() = mutableMapOf<Int, Double>().apply { putAll(bandToFreqMap ) }
}

View File

@ -0,0 +1,11 @@
package io.casey.musikcube.remote.ui.settings.activity
import io.casey.musikcube.remote.ui.shared.activity.BaseActivity
import io.casey.musikcube.remote.ui.shared.extension.slideThisRight
class RemoteEqActivity: BaseActivity() {
override fun finish() {
super.finish()
slideThisRight()
}
}

View File

@ -13,6 +13,7 @@ import io.casey.musikcube.remote.service.websocket.model.ReplayGainMode
import io.casey.musikcube.remote.service.websocket.model.TransportType
import io.casey.musikcube.remote.ui.settings.viewmodel.RemoteSettingsViewModel
import io.casey.musikcube.remote.ui.shared.activity.BaseActivity
import io.casey.musikcube.remote.ui.shared.extension.slideNextLeft
import io.casey.musikcube.remote.ui.shared.extension.slideThisDown
import io.casey.musikcube.remote.ui.shared.mixin.DataProviderMixin
import io.casey.musikcube.remote.ui.shared.mixin.ViewModelMixin
@ -30,6 +31,7 @@ class RemoteSettingsActivity: BaseActivity() {
private lateinit var replayGainSpinner: Spinner
private lateinit var preampSeekbar: SeekBar
private lateinit var preampTextView: TextView
private lateinit var configureEq: TextView
private lateinit var reindexButton: TextView
private lateinit var rebuildButton: TextView
@ -51,6 +53,7 @@ class RemoteSettingsActivity: BaseActivity() {
replayGainSpinner = findViewById(R.id.replaygain_spinner)
preampSeekbar = findViewById(R.id.gain_seekbar)
preampTextView = findViewById(R.id.gain_textview)
configureEq = findViewById(R.id.configure_eq_button)
reindexButton = findViewById(R.id.reindex_button)
rebuildButton = findViewById(R.id.rebuild_button)
initListeners()
@ -137,21 +140,21 @@ class RemoteSettingsActivity: BaseActivity() {
private fun initListeners() {
/* metadata */
reindexButton.setOnClickListener({
reindexButton.setOnClickListener {
reindexButton.isEnabled = false
data.provider.reindexMetadata().subscribeBy(
onNext = { if (!it) reindexButton.isEnabled = true },
onError = { reindexButton.isEnabled = true }
)
})
}
rebuildButton.setOnClickListener({
rebuildButton.setOnClickListener {
rebuildButton.isEnabled = false
data.provider.rebuildMetadata().subscribeBy(
onNext = { if (!it) rebuildButton.isEnabled = true },
onError = { rebuildButton.isEnabled = true }
)
})
}
/* devices */
driverSpinner.onItemSelectedListener = driverChangeListener
@ -180,6 +183,13 @@ class RemoteSettingsActivity: BaseActivity() {
preampSeekbar.progress = 2000
/* equalizer */
configureEq.setOnClickListener {
val intent = Intent(this, RemoteEqActivity::class.java)
startActivity(intent)
slideNextLeft()
}
/* transport */
val transportModes = ArrayAdapter.createFromResource(
this, R.array.transport_type_array,

View File

@ -227,6 +227,10 @@ fun AppCompatActivity.slideNextUp() = overridePendingTransition(R.anim.slide_up,
fun AppCompatActivity.slideThisDown() = overridePendingTransition(R.anim.stay_put, R.anim.slide_down)
fun AppCompatActivity.slideNextLeft() = overridePendingTransition(R.anim.slide_left, R.anim.stay_put)
fun AppCompatActivity.slideThisRight() = overridePendingTransition(R.anim.stay_put, R.anim.slide_right)
fun <T1: Any, T2: Any, R: Any> letMany(p1: T1?, p2: T2?, block: (T1, T2) -> R?): R? {
return if (p1 != null && p2 != null) block(p1, p2) else null
}

View File

@ -0,0 +1,7 @@
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="250"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromXDelta="100%"
android:toXDelta="0" />
</set>

View File

@ -0,0 +1,7 @@
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="250"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromXDelta="0"
android:toXDelta="100%" />
</set>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/loading_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:background="@color/theme_button_background_transparent">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
</FrameLayout>
</FrameLayout>

View File

@ -103,6 +103,23 @@
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginRight="8dp"
android:text="@string/remote_settings_equalizer"/>
<TextView
style="@style/ConnectionButton"
android:id="@+id/configure_eq_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginLeft="24dp"
android:layout_marginBottom="8dp"
android:text="@string/remote_settings_configure_eq_button"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -131,6 +131,8 @@
<string name="remote_settings_reindex_button">rescan</string>
<string name="remote_settings_rebuild_button">rebuild</string>
<string name="remote_settings_transport_type">playback transport</string>
<string name="remote_settings_equalizer">equalizer</string>
<string name="remote_settings_configure_eq_button">configure</string>
<string name="transport_type_gapless">gapless</string>
<string name="transport_type_crossfade">crossfade</string>
<string name="replaygain_mode_disabled">disabled</string>

View File

@ -136,6 +136,8 @@ namespace key {
static const std::string device_id = "device_id";
static const std::string replaygain_mode = "replaygain_mode";
static const std::string preamp_gain = "preamp_gain";
static const std::string enabled = "enabled";
static const std::string bands = "bands";
static const std::string time = "time";
}
@ -195,6 +197,8 @@ namespace request {
static const std::string set_default_output_driver = "set_default_output_driver";
static const std::string get_gain_settings = "get_gain_settings";
static const std::string set_gain_settings = "set_gain_settings";
static const std::string get_equalizer_settings = "get_equalizer_settings";
static const std::string set_equalizer_settings = "set_equalizer_settings";
static const std::string get_transport_type = "get_transport_type";
static const std::string set_transport_type = "set_transport_type";
static const std::string snapshot_play_queue = "snapshot_play_queue";

View File

@ -472,6 +472,14 @@ void WebSocketServer::HandleRequest(connection_hdl connection, json& request) {
this->RespondWithSetGainSettings(connection, request);
return;
}
else if (name == request::get_equalizer_settings) {
this->RespondWithGetEqualizerSettings(connection, request);
return;
}
else if (name == request::set_equalizer_settings) {
this->RespondWithSetEqualizerSettings(connection, request);
return;
}
else if (name == request::get_transport_type) {
this->RespondWithGetTransportType(connection, request);
return;
@ -1422,6 +1430,42 @@ void WebSocketServer::RespondWithSetGainSettings(connection_hdl connection, json
this->RespondWithSuccess(connection, request);
}
void WebSocketServer::RespondWithGetEqualizerSettings(connection_hdl connection, json& request) {
double values[EqualizerBandCount];
context.environment->GetEqualizerBandValues(values, EqualizerBandCount);
const bool enabled = context.environment->GetEqualizerEnabled();
std::map<std::string, double> freqToValue;
for (size_t i = 0; i < EqualizerBandCount; i++) {
freqToValue[std::to_string(EqualizerBands[i])] = values[i];
}
this->RespondWithOptions(connection, request, {
{ key::enabled, enabled },
{ key::bands, freqToValue }
});
}
void WebSocketServer::RespondWithSetEqualizerSettings(connection_hdl connection, json& request) {
if (request.find("enabled") != request.end()) {
bool enabled = request.value("enabled", false);
context.environment->SetEqualizerEnabled(enabled);
}
if (request.find("bands") != request.end()) {
auto bands = request.value("bands", json::array());
if (bands.size() == EqualizerBandCount) {
double values[EqualizerBandCount];
for (size_t i = 0; i < EqualizerBandCount; i++) {
values[i] = bands[i];
context.environment->SetEqualizerBandValues(values, EqualizerBandCount);
}
}
}
this->RespondWithSuccess(connection, request);
}
void WebSocketServer::RespondWithGetTransportType(connection_hdl connection, json& request) {
auto type = context.environment->GetTransportType();
this->RespondWithOptions(connection, request, {

View File

@ -164,6 +164,8 @@ class WebSocketServer {
void RespondWithSetDefaultOutputDriver(connection_hdl connection, json& request);
void RespondWithGetGainSettings(connection_hdl connection, json& request);
void RespondWithSetGainSettings(connection_hdl connection, json& request);
void RespondWithGetEqualizerSettings(connection_hdl connection, json& request);
void RespondWithSetEqualizerSettings(connection_hdl connection, json& request);
void RespondWithGetTransportType(connection_hdl connection, json& request);
void RespondWithSetTransportType(connection_hdl connection, json& request);
void RespondWithSnapshotPlayQueue(connection_hdl connection, json& request);