From 5fb39658f17d1eafeb745a316112440944c3a507 Mon Sep 17 00:00:00 2001
From: Matthew Leon <ml@matthewleon.com>
Date: Mon, 21 Aug 2017 17:10:12 +0100
Subject: [PATCH] OSX mixer

---
 AUTHORS                                |  1 +
 Makefile.am                            |  2 +
 NEWS                                   |  2 +
 src/mixer/MixerList.hxx                |  1 +
 src/mixer/plugins/OSXMixerPlugin.cxx   | 69 ++++++++++++++++++++++++++
 src/output/plugins/OSXOutputPlugin.cxx | 57 ++++++++++++++++++++-
 src/output/plugins/OSXOutputPlugin.hxx |  8 +++
 7 files changed, 139 insertions(+), 1 deletion(-)
 create mode 100644 src/mixer/plugins/OSXMixerPlugin.cxx

diff --git a/AUTHORS b/AUTHORS
index e4582d0ec..14aa053fa 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -30,3 +30,4 @@ The following people have contributed code to MPD:
  Jurgen Kramer <gtmkramer@xs4all.nl>
  Jean-Francois Dockes <jf@dockes.org>
  Yue Wang <yuleopen@gmail.com>
+ Matthew Leon Grinshpun <ml@matthewleon.com>
diff --git a/Makefile.am b/Makefile.am
index c5527458c..544fddeb6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1490,6 +1490,8 @@ liboutput_plugins_a_SOURCES += \
 	src/output/plugins/OSXOutputPlugin.cxx \
 	src/output/plugins/OSXOutputPlugin.hxx
 endif
+libmixer_plugins_a_SOURCES += \
+	src/mixer/plugins/OSXMixerPlugin.cxx
 
 if ENABLE_PULSE
 liboutput_plugins_a_SOURCES += \
diff --git a/NEWS b/NEWS
index 2b8dceb5b..6c1ac7d82 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,8 @@ ver 0.20.10 (not yet released)
   - ffmpeg: support MusicBrainz ID3v2 tags
 * tags
   - aiff: fix FORM chunk size endianess (is big-endian)
+* mixer
+  - osx: add a mixer for OSX.
 * fix crash on Windows
 
 ver 0.20.9 (2017/06/04)
diff --git a/src/mixer/MixerList.hxx b/src/mixer/MixerList.hxx
index b1a8cf11a..7c5fe24b2 100644
--- a/src/mixer/MixerList.hxx
+++ b/src/mixer/MixerList.hxx
@@ -32,6 +32,7 @@ extern const MixerPlugin software_mixer_plugin;
 extern const MixerPlugin alsa_mixer_plugin;
 extern const MixerPlugin haiku_mixer_plugin;
 extern const MixerPlugin oss_mixer_plugin;
+extern const MixerPlugin osx_mixer_plugin;
 extern const MixerPlugin roar_mixer_plugin;
 extern const MixerPlugin pulse_mixer_plugin;
 extern const MixerPlugin winmm_mixer_plugin;
diff --git a/src/mixer/plugins/OSXMixerPlugin.cxx b/src/mixer/plugins/OSXMixerPlugin.cxx
new file mode 100644
index 000000000..566489b26
--- /dev/null
+++ b/src/mixer/plugins/OSXMixerPlugin.cxx
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2003-2017 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "mixer/MixerInternal.hxx"
+#include "output/plugins/OSXOutputPlugin.hxx"
+
+class OSXMixer final : public Mixer {
+	OSXOutput &output;
+
+public:
+	OSXMixer(OSXOutput &_output, MixerListener &_listener)
+		:Mixer(osx_mixer_plugin, _listener),
+		 output(_output)
+	{
+	}
+
+	/* virtual methods from class Mixer */
+	void Open() override {
+	}
+
+	void Close() override {
+	}
+
+	int GetVolume() override;
+	void SetVolume(unsigned volume) override;
+};
+
+int
+OSXMixer::GetVolume()
+{
+	return osx_output_get_volume(output);
+}
+
+void
+OSXMixer::SetVolume(unsigned new_volume)
+{
+	osx_output_set_volume(output, new_volume);
+}
+
+static Mixer *
+osx_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao,
+		MixerListener &listener,
+		gcc_unused const ConfigBlock &block)
+{
+	OSXOutput &osxo = (OSXOutput &)ao;
+	return new OSXMixer(osxo, listener);
+}
+
+const MixerPlugin osx_mixer_plugin = {
+	osx_mixer_init,
+	true,
+};
diff --git a/src/output/plugins/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx
index 020044a47..3a0e6fbee 100644
--- a/src/output/plugins/OSXOutputPlugin.cxx
+++ b/src/output/plugins/OSXOutputPlugin.cxx
@@ -20,6 +20,7 @@
 #include "config.h"
 #include "OSXOutputPlugin.hxx"
 #include "../OutputAPI.hxx"
+#include "mixer/MixerList.hxx"
 #include "util/ScopeExit.hxx"
 #include "util/RuntimeError.hxx"
 #include "util/Domain.hxx"
@@ -53,6 +54,9 @@ struct OSXOutput {
 	boost::lockfree::spsc_queue<uint8_t> *ring_buffer;
 
 	OSXOutput(const ConfigBlock &block);
+
+	int GetVolume();
+	void SetVolume(unsigned new_volume);
 };
 
 static constexpr Domain osx_output_domain("osx_output");
@@ -103,6 +107,44 @@ OSXOutput::OSXOutput(const ConfigBlock &block)
 	sync_sample_rate = block.GetBlockValue("sync_sample_rate", false);
 }
 
+int
+OSXOutput::GetVolume()
+{
+	AudioUnitParameterValue dvolume;
+	char errormsg[1024];
+
+	OSStatus status = AudioUnitGetParameter(au, kHALOutputParam_Volume,
+			kAudioUnitScope_Global, 0, &dvolume);
+	if (status != noErr) {
+		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
+		throw FormatRuntimeError("unable to get volume: %s", errormsg);
+	}
+
+	/* see the explanation in SetVolume, below */
+	return static_cast<int>(dvolume * dvolume * 100.0);
+}
+
+void
+OSXOutput::SetVolume(unsigned new_volume) {
+	char errormsg[1024];
+
+	/* The scaling below makes shifts in volume greater at the lower end
+	 * of the scale. This mimics the "feel" of physical volume levers. This is
+	 * generally what users of audio software expect.
+	 */
+
+	AudioUnitParameterValue scaled_volume =
+		sqrt(static_cast<AudioUnitParameterValue>(new_volume) / 100.0);
+
+	OSStatus status = AudioUnitSetParameter(au, kHALOutputParam_Volume,
+			kAudioUnitScope_Global, 0, scaled_volume, 0);
+	if (status != noErr) {
+		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
+		throw FormatRuntimeError( "unable to set new volume %u: %s",
+				new_volume, errormsg);
+	}
+}
+
 static AudioOutput *
 osx_output_init(const ConfigBlock &block)
 {
@@ -678,6 +720,18 @@ osx_output_delay(AudioOutput *ao) noexcept
 		: std::chrono::milliseconds(25);
 }
 
+int
+osx_output_get_volume(OSXOutput &output)
+{
+	return output.GetVolume();
+}
+
+void
+osx_output_set_volume(OSXOutput &output, unsigned new_volume)
+{
+	return output.SetVolume(new_volume);
+}
+
 const struct AudioOutputPlugin osx_output_plugin = {
 	"osx",
 	osx_output_test_default_device,
@@ -693,5 +747,6 @@ const struct AudioOutputPlugin osx_output_plugin = {
 	nullptr,
 	nullptr,
 	nullptr,
-	nullptr,
+
+	&osx_mixer_plugin,
 };
diff --git a/src/output/plugins/OSXOutputPlugin.hxx b/src/output/plugins/OSXOutputPlugin.hxx
index 440cb8de1..9c98424b2 100644
--- a/src/output/plugins/OSXOutputPlugin.hxx
+++ b/src/output/plugins/OSXOutputPlugin.hxx
@@ -20,6 +20,14 @@
 #ifndef MPD_OSX_OUTPUT_PLUGIN_HXX
 #define MPD_OSX_OUTPUT_PLUGIN_HXX
 
+struct OSXOutput;
+
 extern const struct AudioOutputPlugin osx_output_plugin;
 
+int
+osx_output_get_volume(OSXOutput &output);
+
+void
+osx_output_set_volume(OSXOutput &output, unsigned new_volume);
+
 #endif