From b4c517c50179692bdcd7ff42f4f5c28092005c41 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Thu, 2 Aug 2018 21:24:52 +0200
Subject: [PATCH] song/AudioFormatFilter: add mask support

---
 doc/protocol.xml                   |  8 ++++++++
 src/song/AudioFormatSongFilter.cxx |  9 +++++----
 src/song/Filter.cxx                | 12 ++++++++----
 3 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/doc/protocol.xml b/doc/protocol.xml
index f68375c6b..eb6e16bb6 100644
--- a/doc/protocol.xml
+++ b/doc/protocol.xml
@@ -276,6 +276,14 @@
           </para>
         </listitem>
 
+        <listitem>
+          <para>
+            "<code>(AudioFormat =~ 'SAMPLERATE:BITS:CHANNELS')</code>":
+            matches the audio format with the given mask (i.e. one
+            or more attributes may be "<code>*</code>").
+          </para>
+        </listitem>
+
         <listitem>
           <para>
             "<code>(!EXPRESSION)</code>": negate an expression.
diff --git a/src/song/AudioFormatSongFilter.cxx b/src/song/AudioFormatSongFilter.cxx
index 6ea8dd250..327100eb1 100644
--- a/src/song/AudioFormatSongFilter.cxx
+++ b/src/song/AudioFormatSongFilter.cxx
@@ -24,13 +24,14 @@
 std::string
 AudioFormatSongFilter::ToExpression() const noexcept
 {
-	// TODO: support mask
-	return std::string("(AudioFormat == \"") + ToString(value).c_str() + "\")";
+	return std::string("(AudioFormat ") +
+		(value.IsFullyDefined() ? "==" : "=~") +
+		" \"" + ToString(value).c_str() + "\")";
 }
 
 bool
 AudioFormatSongFilter::Match(const LightSong &song) const noexcept
 {
-	// TODO: support mask
-	return song.audio_format == value;
+	return song.audio_format.IsDefined() &&
+		song.audio_format.MatchMask(value);
 }
diff --git a/src/song/Filter.cxx b/src/song/Filter.cxx
index df943b8d6..ff1be66ee 100644
--- a/src/song/Filter.cxx
+++ b/src/song/Filter.cxx
@@ -245,14 +245,18 @@ SongFilter::ParseExpression(const char *&s, bool fold_case)
 
 		return std::make_unique<BaseSongFilter>(std::move(value));
 	} else if (type == LOCATE_TAG_AUDIO_FORMAT) {
-		if (s[0] != '=' || s[1] != '=')
-			throw std::runtime_error("'==' expected");
+		bool mask;
+		if (s[0] == '=' && s[1] == '=')
+			mask = false;
+		else if (s[0] == '=' && s[1] == '~')
+			mask = true;
+		else
+			throw std::runtime_error("'==' or '=~' expected");
 
 		s = StripLeft(s + 2);
 
-		// TODO: support mask
 		const auto value = ParseAudioFormat(ExpectQuoted(s).c_str(),
-						    false);
+						    mask);
 
 		if (*s != ')')
 			throw std::runtime_error("')' expected");