diff --git a/src/filter/plugins/NormalizeFilterPlugin.cxx b/src/filter/plugins/NormalizeFilterPlugin.cxx
index 868787a09..e87ab637a 100644
--- a/src/filter/plugins/NormalizeFilterPlugin.cxx
+++ b/src/filter/plugins/NormalizeFilterPlugin.cxx
@@ -8,8 +8,7 @@
 #include "pcm/Buffer.hxx"
 #include "pcm/AudioFormat.hxx"
 #include "pcm/Normalizer.hxx"
-
-#include <string.h>
+#include "util/SpanCast.hxx"
 
 class NormalizeFilter final : public Filter {
 	PcmNormalizer normalizer;
@@ -49,13 +48,13 @@ PreparedNormalizeFilter::Open(AudioFormat &audio_format)
 }
 
 std::span<const std::byte>
-NormalizeFilter::FilterPCM(std::span<const std::byte> src)
+NormalizeFilter::FilterPCM(std::span<const std::byte> _src)
 {
-	auto *dest = (int16_t *)buffer.Get(src.size());
-	memcpy(dest, src.data(), src.size());
+	const auto src = FromBytesStrict<const int16_t>(_src);
+	auto *dest = (int16_t *)buffer.GetT<int16_t>(src.size());
 
-	normalizer.ProcessS16({dest, src.size() / 2});
-	return { (const std::byte *)dest, src.size() };
+	normalizer.ProcessS16(dest, src);
+	return std::as_bytes(std::span{dest, src.size()});
 }
 
 const FilterPlugin normalize_filter_plugin = {
diff --git a/src/pcm/Normalizer.cxx b/src/pcm/Normalizer.cxx
index cb15ca27b..164d0fef9 100644
--- a/src/pcm/Normalizer.cxx
+++ b/src/pcm/Normalizer.cxx
@@ -6,9 +6,11 @@
 #include "Clamp.hxx"
 #include "SampleFormat.hxx"
 #include "Traits.hxx"
+#include "util/Compiler.h"
 
 void
-PcmNormalizer::ProcessS16(const std::span<int16_t> audio) noexcept
+PcmNormalizer::ProcessS16(int16_t *gcc_restrict dest,
+			  const std::span<const int16_t> src) noexcept
 {
 	constexpr SampleFormat format = SampleFormat::S16;
 	using Traits = SampleTraits<format>;
@@ -16,8 +18,8 @@ PcmNormalizer::ProcessS16(const std::span<int16_t> audio) noexcept
         const int slot = (pos + 1) % bufsz;
 
         int peakVal = 1, peakPos = 0;
-	for (std::size_t i = 0; i < audio.size(); i++) {
-		int val = audio[i];
+	for (std::size_t i = 0; i < src.size(); i++) {
+		int val = src[i];
                 if (val < 0)
                         val = -val;
 		if (val > peakVal)
@@ -52,7 +54,7 @@ PcmNormalizer::ProcessS16(const std::span<int16_t> audio) noexcept
 		newGain = 1 << 10;
 
         //! Make sure the adjusted gain won't cause clipping
-        std::size_t ramp = audio.size();
+        std::size_t ramp = src.size();
         if ((peakVal*newGain >> 10) > Traits::MAX)
         {
                 newGain = (Traits::MAX << 10)/peakVal;
@@ -69,9 +71,9 @@ PcmNormalizer::ProcessS16(const std::span<int16_t> audio) noexcept
                 curGain = 1 << 10;
 	const int delta = (newGain - curGain) / (int)ramp;
 
-	for (std::size_t i = 0; i < audio.size(); i++) {
+	for (std::size_t i = 0; i < src.size(); i++) {
 		//! Amplify the sample
-		audio[i] = PcmClamp<format>(audio[i] * curGain >> 10);
+		*dest++ = PcmClamp<format>(src[i] * curGain >> 10);
 
                 //! Adjust the gain
                 if (i < ramp)
diff --git a/src/pcm/Normalizer.hxx b/src/pcm/Normalizer.hxx
index 0c826343d..93dff4f81 100644
--- a/src/pcm/Normalizer.hxx
+++ b/src/pcm/Normalizer.hxx
@@ -41,7 +41,7 @@ public:
 	}
 
 	//! Process 16-bit signed data
-	void ProcessS16(std::span<int16_t> audio) noexcept;
+	void ProcessS16(int16_t *dest, std::span<const int16_t> src) noexcept;
 };
 
 //! TODO: Compressor_Process_int32, Compressor_Process_float, others as needed
diff --git a/test/run_normalize.cxx b/test/run_normalize.cxx
index ac8bfd140..592209b71 100644
--- a/test/run_normalize.cxx
+++ b/test/run_normalize.cxx
@@ -37,8 +37,9 @@ try {
 	static std::byte buffer[4096];
 	ssize_t nbytes;
 	while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
-		normalizer.ProcessS16(FromBytesStrict<int16_t>(std::span{buffer}.first(nbytes)));
-		[[maybe_unused]] ssize_t ignored = write(1, buffer, nbytes);
+		static int16_t dest[2048];
+		normalizer.ProcessS16(dest, FromBytesStrict<const int16_t>(std::span{buffer}.first(nbytes)));
+		[[maybe_unused]] ssize_t ignored = write(1, dest, nbytes);
 	}
 
 	return EXIT_SUCCESS;