From b488204093d8fcabcb4a523da17f53276946888b Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Mon, 21 Nov 2016 22:14:09 +0100
Subject: [PATCH] decoder/API: move DecoderBridge methods to Bridge.cxx

---
 Makefile.am                |   2 +-
 src/decoder/Bridge.cxx     | 467 +++++++++++++++++++++++++++++++++++++
 src/decoder/DecoderAPI.cxx | 467 -------------------------------------
 3 files changed, 468 insertions(+), 468 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index e27e179fe..9fc58617c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -99,7 +99,6 @@ libmpd_a_SOURCES = \
 	src/decoder/DecoderThread.cxx src/decoder/DecoderThread.hxx \
 	src/decoder/DecoderCommand.hxx \
 	src/decoder/DecoderControl.cxx src/decoder/DecoderControl.hxx \
-	src/decoder/DecoderAPI.cxx src/decoder/DecoderAPI.hxx \
 	src/decoder/Client.hxx \
 	src/decoder/DecoderPlugin.hxx \
 	src/decoder/Bridge.cxx src/decoder/Bridge.hxx \
@@ -934,6 +933,7 @@ endif
 libdecoder_a_SOURCES = \
 	src/decoder/plugins/PcmDecoderPlugin.cxx \
 	src/decoder/plugins/PcmDecoderPlugin.hxx \
+	src/decoder/DecoderAPI.cxx src/decoder/DecoderAPI.hxx \
 	src/decoder/Reader.cxx src/decoder/Reader.hxx \
 	src/decoder/DecoderBuffer.cxx src/decoder/DecoderBuffer.hxx \
 	src/decoder/DecoderPlugin.cxx \
diff --git a/src/decoder/Bridge.cxx b/src/decoder/Bridge.cxx
index 3119907c9..314c067bf 100644
--- a/src/decoder/Bridge.cxx
+++ b/src/decoder/Bridge.cxx
@@ -19,14 +19,25 @@
 
 #include "config.h"
 #include "Bridge.hxx"
+#include "DecoderAPI.hxx"
+#include "DecoderError.hxx"
 #include "DecoderControl.hxx"
+#include "DetachedSong.hxx"
 #include "pcm/PcmConvert.hxx"
 #include "MusicPipe.hxx"
 #include "MusicBuffer.hxx"
 #include "MusicChunk.hxx"
+#include "pcm/PcmConvert.hxx"
 #include "tag/Tag.hxx"
+#include "AudioConfig.hxx"
+#include "ReplayGainConfig.hxx"
+#include "Log.hxx"
+#include "input/InputStream.hxx"
+#include "util/ConstBuffer.hxx"
 
 #include <assert.h>
+#include <string.h>
+#include <math.h>
 
 DecoderBridge::~DecoderBridge()
 {
@@ -125,3 +136,459 @@ DecoderBridge::FlushChunk()
 	if (dc.client_is_waiting)
 		dc.client_cond.signal();
 }
+
+bool
+DecoderBridge::PrepareInitialSeek()
+{
+	assert(dc.pipe != nullptr);
+
+	if (dc.state != DecoderState::DECODE)
+		/* wait until the decoder has finished initialisation
+		   (reading file headers etc.) before emitting the
+		   virtual "SEEK" command */
+		return false;
+
+	if (initial_seek_running)
+		/* initial seek has already begun - override any other
+		   command */
+		return true;
+
+	if (initial_seek_pending) {
+		if (!dc.seekable) {
+			/* seeking is not possible */
+			initial_seek_pending = false;
+			return false;
+		}
+
+		if (dc.command == DecoderCommand::NONE) {
+			/* begin initial seek */
+
+			initial_seek_pending = false;
+			initial_seek_running = true;
+			return true;
+		}
+
+		/* skip initial seek when there's another command
+		   (e.g. STOP) */
+
+		initial_seek_pending = false;
+	}
+
+	return false;
+}
+
+DecoderCommand
+DecoderBridge::GetVirtualCommand()
+{
+	if (error)
+		/* an error has occurred: stop the decoder plugin */
+		return DecoderCommand::STOP;
+
+	assert(dc.pipe != nullptr);
+
+	if (PrepareInitialSeek())
+		return DecoderCommand::SEEK;
+
+	return dc.command;
+}
+
+DecoderCommand
+DecoderBridge::LockGetVirtualCommand()
+{
+	const ScopeLock protect(dc.mutex);
+	return GetVirtualCommand();
+}
+
+DecoderCommand
+DecoderBridge::DoSendTag(const Tag &tag)
+{
+	if (current_chunk != nullptr) {
+		/* there is a partial chunk - flush it, we want the
+		   tag in a new chunk */
+		FlushChunk();
+	}
+
+	assert(current_chunk == nullptr);
+
+	auto *chunk = GetChunk();
+	if (chunk == nullptr) {
+		assert(dc.command != DecoderCommand::NONE);
+		return dc.command;
+	}
+
+	chunk->tag = new Tag(tag);
+	return DecoderCommand::NONE;
+}
+
+bool
+DecoderBridge::UpdateStreamTag(InputStream *is)
+{
+	auto *tag = is != nullptr
+		? is->LockReadTag()
+		: nullptr;
+	if (tag == nullptr) {
+		tag = song_tag;
+		if (tag == nullptr)
+			return false;
+
+		/* no stream tag present - submit the song tag
+		   instead */
+	} else
+		/* discard the song tag; we don't need it */
+		delete song_tag;
+
+	song_tag = nullptr;
+
+	delete stream_tag;
+	stream_tag = tag;
+	return true;
+}
+
+void
+DecoderBridge::Ready(const AudioFormat audio_format,
+		     bool seekable, SignedSongTime duration)
+{
+	struct audio_format_string af_string;
+
+	assert(dc.state == DecoderState::START);
+	assert(dc.pipe != nullptr);
+	assert(dc.pipe->IsEmpty());
+	assert(convert == nullptr);
+	assert(stream_tag == nullptr);
+	assert(decoder_tag == nullptr);
+	assert(!seeking);
+	assert(audio_format.IsDefined());
+	assert(audio_format.IsValid());
+
+	dc.in_audio_format = audio_format;
+	dc.out_audio_format = getOutputAudioFormat(audio_format);
+
+	dc.seekable = seekable;
+	dc.total_time = duration;
+
+	FormatDebug(decoder_domain, "audio_format=%s, seekable=%s",
+		    audio_format_to_string(dc.in_audio_format, &af_string),
+		    seekable ? "true" : "false");
+
+	if (dc.in_audio_format != dc.out_audio_format) {
+		FormatDebug(decoder_domain, "converting to %s",
+			    audio_format_to_string(dc.out_audio_format,
+						   &af_string));
+
+		convert = new PcmConvert();
+
+		try {
+			convert->Open(dc.in_audio_format,
+					      dc.out_audio_format);
+		} catch (...) {
+			error = std::current_exception();
+		}
+	}
+
+	const ScopeLock protect(dc.mutex);
+	dc.state = DecoderState::DECODE;
+	dc.client_cond.signal();
+}
+
+DecoderCommand
+DecoderBridge::GetCommand()
+{
+	return LockGetVirtualCommand();
+}
+
+void
+DecoderBridge::CommandFinished()
+{
+	const ScopeLock protect(dc.mutex);
+
+	assert(dc.command != DecoderCommand::NONE || initial_seek_running);
+	assert(dc.command != DecoderCommand::SEEK ||
+	       initial_seek_running ||
+	       dc.seek_error || seeking);
+	assert(dc.pipe != nullptr);
+
+	if (initial_seek_running) {
+		assert(!seeking);
+		assert(current_chunk == nullptr);
+		assert(dc.pipe->IsEmpty());
+
+		initial_seek_running = false;
+		timestamp = dc.start_time.ToDoubleS();
+		return;
+	}
+
+	if (seeking) {
+		seeking = false;
+
+		/* delete frames from the old song position */
+
+		if (current_chunk != nullptr) {
+			dc.buffer->Return(current_chunk);
+			current_chunk = nullptr;
+		}
+
+		dc.pipe->Clear(*dc.buffer);
+
+		timestamp = dc.seek_time.ToDoubleS();
+	}
+
+	dc.command = DecoderCommand::NONE;
+	dc.client_cond.signal();
+}
+
+SongTime
+DecoderBridge::GetSeekTime()
+{
+	assert(dc.pipe != nullptr);
+
+	if (initial_seek_running)
+		return dc.start_time;
+
+	assert(dc.command == DecoderCommand::SEEK);
+
+	seeking = true;
+
+	return dc.seek_time;
+}
+
+uint64_t
+DecoderBridge::GetSeekFrame()
+{
+	return GetSeekTime().ToScale<uint64_t>(dc.in_audio_format.sample_rate);
+}
+
+void
+DecoderBridge::SeekError()
+{
+	assert(dc.pipe != nullptr);
+
+	if (initial_seek_running) {
+		/* d'oh, we can't seek to the sub-song start position,
+		   what now? - no idea, ignoring the problem for now. */
+		initial_seek_running = false;
+		return;
+	}
+
+	assert(dc.command == DecoderCommand::SEEK);
+
+	dc.seek_error = true;
+	seeking = false;
+
+	CommandFinished();
+}
+
+InputStreamPtr
+DecoderBridge::OpenUri(const char *uri)
+{
+	assert(dc.state == DecoderState::START ||
+	       dc.state == DecoderState::DECODE);
+
+	Mutex &mutex = dc.mutex;
+	Cond &cond = dc.cond;
+
+	auto is = InputStream::Open(uri, mutex, cond);
+
+	const ScopeLock lock(mutex);
+	while (true) {
+		is->Update();
+		if (is->IsReady())
+			return is;
+
+		if (dc.command == DecoderCommand::STOP)
+			throw StopDecoder();
+
+		cond.wait(mutex);
+	}
+}
+
+void
+DecoderBridge::SubmitTimestamp(double t)
+{
+	assert(t >= 0);
+
+	timestamp = t;
+}
+
+DecoderCommand
+DecoderBridge::SubmitData(InputStream *is,
+			  const void *data, size_t length,
+			  uint16_t kbit_rate)
+{
+	assert(dc.state == DecoderState::DECODE);
+	assert(dc.pipe != nullptr);
+	assert(length % dc.in_audio_format.GetFrameSize() == 0);
+
+	DecoderCommand cmd = LockGetVirtualCommand();
+
+	if (cmd == DecoderCommand::STOP || cmd == DecoderCommand::SEEK ||
+	    length == 0)
+		return cmd;
+
+	assert(!initial_seek_pending);
+	assert(!initial_seek_running);
+
+	/* send stream tags */
+
+	if (UpdateStreamTag(is)) {
+		if (decoder_tag != nullptr) {
+			/* merge with tag from decoder plugin */
+			Tag *tag = Tag::Merge(*decoder_tag,
+					      *stream_tag);
+			cmd = DoSendTag(*tag);
+			delete tag;
+		} else
+			/* send only the stream tag */
+			cmd = DoSendTag(*stream_tag);
+
+		if (cmd != DecoderCommand::NONE)
+			return cmd;
+	}
+
+	if (convert != nullptr) {
+		assert(dc.in_audio_format != dc.out_audio_format);
+
+		try {
+			auto result = convert->Convert({data, length});
+			data = result.data;
+			length = result.size;
+		} catch (const std::runtime_error &e) {
+			/* the PCM conversion has failed - stop
+			   playback, since we have no better way to
+			   bail out */
+			error = std::current_exception();
+			return DecoderCommand::STOP;
+		}
+	} else {
+		assert(dc.in_audio_format == dc.out_audio_format);
+	}
+
+	while (length > 0) {
+		bool full;
+
+		auto *chunk = GetChunk();
+		if (chunk == nullptr) {
+			assert(dc.command != DecoderCommand::NONE);
+			return dc.command;
+		}
+
+		const auto dest =
+			chunk->Write(dc.out_audio_format,
+				     SongTime::FromS(timestamp) -
+				     dc.song->GetStartTime(),
+				     kbit_rate);
+		if (dest.IsEmpty()) {
+			/* the chunk is full, flush it */
+			FlushChunk();
+			continue;
+		}
+
+		const size_t nbytes = std::min(dest.size, length);
+
+		/* copy the buffer */
+
+		memcpy(dest.data, data, nbytes);
+
+		/* expand the music pipe chunk */
+
+		full = chunk->Expand(dc.out_audio_format, nbytes);
+		if (full) {
+			/* the chunk is full, flush it */
+			FlushChunk();
+		}
+
+		data = (const uint8_t *)data + nbytes;
+		length -= nbytes;
+
+		timestamp += (double)nbytes /
+			dc.out_audio_format.GetTimeToSize();
+
+		if (dc.end_time.IsPositive() &&
+		    timestamp >= dc.end_time.ToDoubleS())
+			/* the end of this range has been reached:
+			   stop decoding */
+			return DecoderCommand::STOP;
+	}
+
+	return DecoderCommand::NONE;
+}
+
+DecoderCommand
+DecoderBridge::SubmitTag(InputStream *is, Tag &&tag)
+{
+	DecoderCommand cmd;
+
+	assert(dc.state == DecoderState::DECODE);
+	assert(dc.pipe != nullptr);
+
+	/* save the tag */
+
+	delete decoder_tag;
+	decoder_tag = new Tag(std::move(tag));
+
+	/* check for a new stream tag */
+
+	UpdateStreamTag(is);
+
+	/* check if we're seeking */
+
+	if (PrepareInitialSeek())
+		/* during initial seek, no music chunk must be created
+		   until seeking is finished; skip the rest of the
+		   function here */
+		return DecoderCommand::SEEK;
+
+	/* send tag to music pipe */
+
+	if (stream_tag != nullptr) {
+		/* merge with tag from input stream */
+		Tag *merged;
+
+		merged = Tag::Merge(*stream_tag, *decoder_tag);
+		cmd = DoSendTag(*merged);
+		delete merged;
+	} else
+		/* send only the decoder tag */
+		cmd = DoSendTag(*decoder_tag);
+
+	return cmd;
+}
+
+void
+DecoderBridge::SubmitReplayGain(const ReplayGainInfo *new_replay_gain_info)
+{
+	if (new_replay_gain_info != nullptr) {
+		static unsigned serial;
+		if (++serial == 0)
+			serial = 1;
+
+		if (REPLAY_GAIN_OFF != replay_gain_mode) {
+			ReplayGainMode rgm = replay_gain_mode;
+			if (rgm != REPLAY_GAIN_ALBUM)
+				rgm = REPLAY_GAIN_TRACK;
+
+			const auto &tuple = new_replay_gain_info->tuples[rgm];
+			const auto scale =
+				tuple.CalculateScale(replay_gain_preamp,
+						     replay_gain_missing_preamp,
+						     replay_gain_limit);
+			dc.replay_gain_db = 20.0 * log10f(scale);
+		}
+
+		replay_gain_info = *new_replay_gain_info;
+		replay_gain_serial = serial;
+
+		if (current_chunk != nullptr) {
+			/* flush the current chunk because the new
+			   replay gain values affect the following
+			   samples */
+			FlushChunk();
+		}
+	} else
+		replay_gain_serial = 0;
+}
+
+void
+DecoderBridge::SubmitMixRamp(MixRampInfo &&mix_ramp)
+{
+	dc.SetMixRamp(std::move(mix_ramp));
+}
diff --git a/src/decoder/DecoderAPI.cxx b/src/decoder/DecoderAPI.cxx
index 975578113..6258b2a0a 100644
--- a/src/decoder/DecoderAPI.cxx
+++ b/src/decoder/DecoderAPI.cxx
@@ -19,242 +19,12 @@
 
 #include "config.h"
 #include "DecoderAPI.hxx"
-#include "DecoderError.hxx"
-#include "pcm/PcmConvert.hxx"
-#include "AudioConfig.hxx"
-#include "ReplayGainConfig.hxx"
-#include "MusicChunk.hxx"
-#include "MusicBuffer.hxx"
-#include "MusicPipe.hxx"
 #include "DecoderControl.hxx"
 #include "Bridge.hxx"
-#include "DetachedSong.hxx"
 #include "input/InputStream.hxx"
-#include "util/ConstBuffer.hxx"
 #include "Log.hxx"
 
 #include <assert.h>
-#include <string.h>
-#include <math.h>
-
-void
-DecoderBridge::Ready(const AudioFormat audio_format,
-		     bool seekable, SignedSongTime duration)
-{
-	struct audio_format_string af_string;
-
-	assert(dc.state == DecoderState::START);
-	assert(dc.pipe != nullptr);
-	assert(dc.pipe->IsEmpty());
-	assert(convert == nullptr);
-	assert(stream_tag == nullptr);
-	assert(decoder_tag == nullptr);
-	assert(!seeking);
-	assert(audio_format.IsDefined());
-	assert(audio_format.IsValid());
-
-	dc.in_audio_format = audio_format;
-	dc.out_audio_format = getOutputAudioFormat(audio_format);
-
-	dc.seekable = seekable;
-	dc.total_time = duration;
-
-	FormatDebug(decoder_domain, "audio_format=%s, seekable=%s",
-		    audio_format_to_string(dc.in_audio_format, &af_string),
-		    seekable ? "true" : "false");
-
-	if (dc.in_audio_format != dc.out_audio_format) {
-		FormatDebug(decoder_domain, "converting to %s",
-			    audio_format_to_string(dc.out_audio_format,
-						   &af_string));
-
-		convert = new PcmConvert();
-
-		try {
-			convert->Open(dc.in_audio_format,
-					      dc.out_audio_format);
-		} catch (...) {
-			error = std::current_exception();
-		}
-	}
-
-	const ScopeLock protect(dc.mutex);
-	dc.state = DecoderState::DECODE;
-	dc.client_cond.signal();
-}
-
-bool
-DecoderBridge::PrepareInitialSeek()
-{
-	assert(dc.pipe != nullptr);
-
-	if (dc.state != DecoderState::DECODE)
-		/* wait until the decoder has finished initialisation
-		   (reading file headers etc.) before emitting the
-		   virtual "SEEK" command */
-		return false;
-
-	if (initial_seek_running)
-		/* initial seek has already begun - override any other
-		   command */
-		return true;
-
-	if (initial_seek_pending) {
-		if (!dc.seekable) {
-			/* seeking is not possible */
-			initial_seek_pending = false;
-			return false;
-		}
-
-		if (dc.command == DecoderCommand::NONE) {
-			/* begin initial seek */
-
-			initial_seek_pending = false;
-			initial_seek_running = true;
-			return true;
-		}
-
-		/* skip initial seek when there's another command
-		   (e.g. STOP) */
-
-		initial_seek_pending = false;
-	}
-
-	return false;
-}
-
-DecoderCommand
-DecoderBridge::GetVirtualCommand()
-{
-	if (error)
-		/* an error has occurred: stop the decoder plugin */
-		return DecoderCommand::STOP;
-
-	assert(dc.pipe != nullptr);
-
-	if (PrepareInitialSeek())
-		return DecoderCommand::SEEK;
-
-	return dc.command;
-}
-
-DecoderCommand
-DecoderBridge::LockGetVirtualCommand()
-{
-	const ScopeLock protect(dc.mutex);
-	return GetVirtualCommand();
-}
-
-DecoderCommand
-DecoderBridge::GetCommand()
-{
-	return LockGetVirtualCommand();
-}
-
-void
-DecoderBridge::CommandFinished()
-{
-	const ScopeLock protect(dc.mutex);
-
-	assert(dc.command != DecoderCommand::NONE || initial_seek_running);
-	assert(dc.command != DecoderCommand::SEEK ||
-	       initial_seek_running ||
-	       dc.seek_error || seeking);
-	assert(dc.pipe != nullptr);
-
-	if (initial_seek_running) {
-		assert(!seeking);
-		assert(current_chunk == nullptr);
-		assert(dc.pipe->IsEmpty());
-
-		initial_seek_running = false;
-		timestamp = dc.start_time.ToDoubleS();
-		return;
-	}
-
-	if (seeking) {
-		seeking = false;
-
-		/* delete frames from the old song position */
-
-		if (current_chunk != nullptr) {
-			dc.buffer->Return(current_chunk);
-			current_chunk = nullptr;
-		}
-
-		dc.pipe->Clear(*dc.buffer);
-
-		timestamp = dc.seek_time.ToDoubleS();
-	}
-
-	dc.command = DecoderCommand::NONE;
-	dc.client_cond.signal();
-}
-
-SongTime
-DecoderBridge::GetSeekTime()
-{
-	assert(dc.pipe != nullptr);
-
-	if (initial_seek_running)
-		return dc.start_time;
-
-	assert(dc.command == DecoderCommand::SEEK);
-
-	seeking = true;
-
-	return dc.seek_time;
-}
-
-uint64_t
-DecoderBridge::GetSeekFrame()
-{
-	return GetSeekTime().ToScale<uint64_t>(dc.in_audio_format.sample_rate);
-}
-
-void
-DecoderBridge::SeekError()
-{
-	assert(dc.pipe != nullptr);
-
-	if (initial_seek_running) {
-		/* d'oh, we can't seek to the sub-song start position,
-		   what now? - no idea, ignoring the problem for now. */
-		initial_seek_running = false;
-		return;
-	}
-
-	assert(dc.command == DecoderCommand::SEEK);
-
-	dc.seek_error = true;
-	seeking = false;
-
-	CommandFinished();
-}
-
-InputStreamPtr
-DecoderBridge::OpenUri(const char *uri)
-{
-	assert(dc.state == DecoderState::START ||
-	       dc.state == DecoderState::DECODE);
-
-	Mutex &mutex = dc.mutex;
-	Cond &cond = dc.cond;
-
-	auto is = InputStream::Open(uri, mutex, cond);
-
-	const ScopeLock lock(mutex);
-	while (true) {
-		is->Update();
-		if (is->IsReady())
-			return is;
-
-		if (dc.command == DecoderCommand::STOP)
-			throw StopDecoder();
-
-		cond.wait(mutex);
-	}
-}
 
 size_t
 decoder_read(DecoderClient *client,
@@ -333,240 +103,3 @@ decoder_skip(DecoderClient *client, InputStream &is, size_t size)
 
 	return true;
 }
-
-void
-DecoderBridge::SubmitTimestamp(double t)
-{
-	assert(t >= 0);
-
-	timestamp = t;
-}
-
-DecoderCommand
-DecoderBridge::DoSendTag(const Tag &tag)
-{
-	if (current_chunk != nullptr) {
-		/* there is a partial chunk - flush it, we want the
-		   tag in a new chunk */
-		FlushChunk();
-	}
-
-	assert(current_chunk == nullptr);
-
-	auto *chunk = GetChunk();
-	if (chunk == nullptr) {
-		assert(dc.command != DecoderCommand::NONE);
-		return dc.command;
-	}
-
-	chunk->tag = new Tag(tag);
-	return DecoderCommand::NONE;
-}
-
-bool
-DecoderBridge::UpdateStreamTag(InputStream *is)
-{
-	auto *tag = is != nullptr
-		? is->LockReadTag()
-		: nullptr;
-	if (tag == nullptr) {
-		tag = song_tag;
-		if (tag == nullptr)
-			return false;
-
-		/* no stream tag present - submit the song tag
-		   instead */
-	} else
-		/* discard the song tag; we don't need it */
-		delete song_tag;
-
-	song_tag = nullptr;
-
-	delete stream_tag;
-	stream_tag = tag;
-	return true;
-}
-
-DecoderCommand
-DecoderBridge::SubmitData(InputStream *is,
-			  const void *data, size_t length,
-			  uint16_t kbit_rate)
-{
-	assert(dc.state == DecoderState::DECODE);
-	assert(dc.pipe != nullptr);
-	assert(length % dc.in_audio_format.GetFrameSize() == 0);
-
-	DecoderCommand cmd = LockGetVirtualCommand();
-
-	if (cmd == DecoderCommand::STOP || cmd == DecoderCommand::SEEK ||
-	    length == 0)
-		return cmd;
-
-	assert(!initial_seek_pending);
-	assert(!initial_seek_running);
-
-	/* send stream tags */
-
-	if (UpdateStreamTag(is)) {
-		if (decoder_tag != nullptr) {
-			/* merge with tag from decoder plugin */
-			Tag *tag = Tag::Merge(*decoder_tag,
-					      *stream_tag);
-			cmd = DoSendTag(*tag);
-			delete tag;
-		} else
-			/* send only the stream tag */
-			cmd = DoSendTag(*stream_tag);
-
-		if (cmd != DecoderCommand::NONE)
-			return cmd;
-	}
-
-	if (convert != nullptr) {
-		assert(dc.in_audio_format != dc.out_audio_format);
-
-		try {
-			auto result = convert->Convert({data, length});
-			data = result.data;
-			length = result.size;
-		} catch (const std::runtime_error &e) {
-			/* the PCM conversion has failed - stop
-			   playback, since we have no better way to
-			   bail out */
-			error = std::current_exception();
-			return DecoderCommand::STOP;
-		}
-	} else {
-		assert(dc.in_audio_format == dc.out_audio_format);
-	}
-
-	while (length > 0) {
-		bool full;
-
-		auto *chunk = GetChunk();
-		if (chunk == nullptr) {
-			assert(dc.command != DecoderCommand::NONE);
-			return dc.command;
-		}
-
-		const auto dest =
-			chunk->Write(dc.out_audio_format,
-				     SongTime::FromS(timestamp) -
-				     dc.song->GetStartTime(),
-				     kbit_rate);
-		if (dest.IsEmpty()) {
-			/* the chunk is full, flush it */
-			FlushChunk();
-			continue;
-		}
-
-		const size_t nbytes = std::min(dest.size, length);
-
-		/* copy the buffer */
-
-		memcpy(dest.data, data, nbytes);
-
-		/* expand the music pipe chunk */
-
-		full = chunk->Expand(dc.out_audio_format, nbytes);
-		if (full) {
-			/* the chunk is full, flush it */
-			FlushChunk();
-		}
-
-		data = (const uint8_t *)data + nbytes;
-		length -= nbytes;
-
-		timestamp += (double)nbytes /
-			dc.out_audio_format.GetTimeToSize();
-
-		if (dc.end_time.IsPositive() &&
-		    timestamp >= dc.end_time.ToDoubleS())
-			/* the end of this range has been reached:
-			   stop decoding */
-			return DecoderCommand::STOP;
-	}
-
-	return DecoderCommand::NONE;
-}
-
-DecoderCommand
-DecoderBridge::SubmitTag(InputStream *is, Tag &&tag)
-{
-	DecoderCommand cmd;
-
-	assert(dc.state == DecoderState::DECODE);
-	assert(dc.pipe != nullptr);
-
-	/* save the tag */
-
-	delete decoder_tag;
-	decoder_tag = new Tag(std::move(tag));
-
-	/* check for a new stream tag */
-
-	UpdateStreamTag(is);
-
-	/* check if we're seeking */
-
-	if (PrepareInitialSeek())
-		/* during initial seek, no music chunk must be created
-		   until seeking is finished; skip the rest of the
-		   function here */
-		return DecoderCommand::SEEK;
-
-	/* send tag to music pipe */
-
-	if (stream_tag != nullptr) {
-		/* merge with tag from input stream */
-		Tag *merged;
-
-		merged = Tag::Merge(*stream_tag, *decoder_tag);
-		cmd = DoSendTag(*merged);
-		delete merged;
-	} else
-		/* send only the decoder tag */
-		cmd = DoSendTag(*decoder_tag);
-
-	return cmd;
-}
-
-void
-DecoderBridge::SubmitReplayGain(const ReplayGainInfo *new_replay_gain_info)
-{
-	if (new_replay_gain_info != nullptr) {
-		static unsigned serial;
-		if (++serial == 0)
-			serial = 1;
-
-		if (REPLAY_GAIN_OFF != replay_gain_mode) {
-			ReplayGainMode rgm = replay_gain_mode;
-			if (rgm != REPLAY_GAIN_ALBUM)
-				rgm = REPLAY_GAIN_TRACK;
-
-			const auto &tuple = new_replay_gain_info->tuples[rgm];
-			const auto scale =
-				tuple.CalculateScale(replay_gain_preamp,
-						     replay_gain_missing_preamp,
-						     replay_gain_limit);
-			dc.replay_gain_db = 20.0 * log10f(scale);
-		}
-
-		replay_gain_info = *new_replay_gain_info;
-		replay_gain_serial = serial;
-
-		if (current_chunk != nullptr) {
-			/* flush the current chunk because the new
-			   replay gain values affect the following
-			   samples */
-			FlushChunk();
-		}
-	} else
-		replay_gain_serial = 0;
-}
-
-void
-DecoderBridge::SubmitMixRamp(MixRampInfo &&mix_ramp)
-{
-	dc.SetMixRamp(std::move(mix_ramp));
-}