Repository: godotengine/godot (language: cpp)

Issue:
Interpolation for sounds is based on resource type (audible distortion with slightly-higher-sample-rate .wav files)
### Godot version

3.4 and 4.1

### System information

Windows 10

### Issue description

.wav sounds are always played back with linear interpolation, and .ogg and .mp3 sounds are always played back with hermite interpolation. This causes noticeable harmonic distortion when playing back 48khz or higher .wav sounds on projects/systems configured to run at 44.1khz. On the other hand, the harmonic distortion on .ogg and .mp3 sounds is almost inaudible.

(This is technically aliasing, not harmonic distortion, but it's aliasing of the harmonic overtones generated by the interpolation algorithm, so calling it harmonic distortion is more intuitive.)

The user currently has no way to control this other than to store multiple sounds for different project sample rates. The interpolation method should either be configurable or just be hermite everywhere.

(If it's just hermite everywhere, then users that want the rich harmonic overtones you can get by using linear interpolation for ~8khz or ~16khz sound effects would instead be expected to pre-upsample to 44.1khz or 48khz with an external program.)

As discussed [here](https://github.com/godotengine/godot/issues/23544#issuecomment-1039655664), this is unlikely to have a significant performance impact on modern systems, because both interpolation methods are cache- and pipeline-friendly, involve relatively few operations, and only involve basic arithmetic (no trig).

Note:

This is distinct from getting aliasing with *extremely* high-sample-rate audio files (e.g. 96khz, 192khz). For example, right now, if you play back a 96khz file of any type that has an exactly 44000hz tone in it in a 44.1khz project, an aliasing tone will be heard at 100hz (with some harmonics). This will happen regardless of interpolation method because a destination nyquist frequency lowpass is not being done. So if someone opens such an issue in the future, it is not a duplicate of this issue, and this issue is not a duplicate of such an issue if such an issue is already open. I'm not sure whether that's should be considered a bug since nobody should really have audio files with audible tones above ~20khz, but it's worth noting.

### Steps to reproduce

Play back a 48khz .wav with a lot of high pitched harmonic information in a 44.1khz godot project. Play back the same 48khz wav but converted to ogg (still 48khz). The .ogg will have significantly less harmonic distortion than the .wav.

https://user-images.githubusercontent.com/585488/154381162-84e84216-bd67-4af3-8fa3-f1fffec8fe07.mp4

![image](https://user-images.githubusercontent.com/585488/154381177-6d48294f-f7d3-46ad-9b39-9d1b727d887e.png)

In this test, there is significantly more distortion around 600hz with the .wav than with the .ogg.

### Minimal reproduction project

No project, but a collection of loose files to test how things "should" sound:

https://github.com/godotengine/godot/files/8064241/entrance_bell_edit_original_and_resampled.zip

Load into a project configured to run at 44.1khz. entrance_bell_edit.wav and entrance_bell_edit.ogg are the original 48khz files. The _resample files were resampled by hand outside of godot to be 44.1khz instead. They both have more distortion, but the cubic/hermite resampling adds significantly less distortion (sounds like how the .ogg sounds in godot), as expected.

(The resampled files are larger because they're 32-bit instead of 16-bit, but that doesn't add any distortion.)


Discussion / hints:
Is this still reproducible in 4.0 RC 3 or later?
Still an issue in Godot 4.x (tested in Godot 4.1.1 mono).

Fix patch:
diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp
index 29ff142ff694..9e262eeaae0a 100644
--- a/scene/resources/audio_stream_wav.cpp
+++ b/scene/resources/audio_stream_wav.cpp
@@ -56,6 +56,7 @@ void AudioStreamPlaybackWAV::start(double p_from_pos) {
 
 	sign = 1;
 	active = true;
+	begin_resample();
 }
 
 void AudioStreamPlaybackWAV::stop() {
@@ -71,7 +72,7 @@ int AudioStreamPlaybackWAV::get_loop_count() const {
 }
 
 double AudioStreamPlaybackWAV::get_playback_position() const {
-	return float(offset >> MIX_FRAC_BITS) / base->mix_rate;
+	return double(offset) / base->mix_rate;
 }
 
 void AudioStreamPlaybackWAV::seek(double p_time) {
@@ -86,20 +87,17 @@ void AudioStreamPlaybackWAV::seek(double p_time) {
 		p_time = max - 0.001;
 	}
 
-	offset = uint64_t(p_time * base->mix_rate) << MIX_FRAC_BITS;
+	offset = int64_t(p_time * base->mix_rate);
 }
 
 template <typename Depth, bool is_stereo, bool is_ima_adpcm, bool is_qoa>
-void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm, QOA_State *p_qoa) {
+void AudioStreamPlaybackWAV::decode_samples(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int8_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm, QOA_State *p_qoa) {
 	// this function will be compiled branchless by any decent compiler
 
-	int32_t final = 0, final_r = 0, next = 0, next_r = 0;
+	int32_t final = 0, final_r = 0;
 	while (p_amount) {
 		p_amount--;
-		int64_t pos = p_offset >> MIX_FRAC_BITS;
-		if (is_stereo && !is_ima_adpcm && !is_qoa) {
-			pos <<= 1;
-		}
+		int64_t pos = p_offset << (is_stereo && !is_ima_adpcm && !is_qoa ? 1 : 0);
 
 		if (is_ima_adpcm) {
 			int64_t sample_pos = pos + p_ima_adpcm[0].window_ofs;
@@ -175,82 +173,32 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst,
 				final_r = p_ima_adpcm[1].predictor;
 			}
 
-		} else {
-			if (is_qoa) {
-				if (pos != p_qoa->cache_pos) { // Prevents triple decoding on lower mix rates.
-					for (int i = 0; i < 2; i++) {
-						// Sign operations prevent triple decoding on backward loops, maxing prevents pop.
-						uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc.samples - 1);
-						uint32_t new_data_ofs = 8 + interp_pos / QOA_FRAME_LEN * p_qoa->frame_len;
-
-						if (p_qoa->data_ofs != new_data_ofs) {
-							p_qoa->data_ofs = new_data_ofs;
-							const uint8_t *ofs_src = (uint8_t *)p_src + p_qoa->data_ofs;
-							qoa_decode_frame(ofs_src, p_qoa->frame_len, &p_qoa->desc, p_qoa->dec.ptr(), &p_qoa->dec_len);
-						}
+		} else if (is_qoa) {
+			uint32_t new_data_ofs = 8 + pos / QOA_FRAME_LEN * p_qoa->frame_len;
 
-						uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc.channels;
-
-						if ((sign > 0 && i == 0) || (sign < 0 && i == 1)) {
-							final = p_qoa->dec[dec_idx];
-							p_qoa->cache[0] = final;
-							if (is_stereo) {
-								final_r = p_qoa->dec[dec_idx + 1];
-								p_qoa->cache_r[0] = final_r;
-							}
-						} else {
-							next = p_qoa->dec[dec_idx];
-							p_qoa->cache[1] = next;
-							if (is_stereo) {
-								next_r = p_qoa->dec[dec_idx + 1];
-								p_qoa->cache_r[1] = next_r;
-							}
-						}
-					}
-					p_qoa->cache_pos = pos;
-				} else {
-					final = p_qoa->cache[0];
-					if (is_stereo) {
-						final_r = p_qoa->cache_r[0];
-					}
-
-					next = p_qoa->cache[1];
-					if (is_stereo) {
-						next_r = p_qoa->cache_r[1];
-					}
-				}
-			} else {
-				final = p_src[pos];
-				if (is_stereo) {
-					final_r = p_src[pos + 1];
-				}
+			if (p_qoa->data_ofs != new_data_ofs) {
+				p_qoa->data_ofs = new_data_ofs;
+				const uint8_t *ofs_src = (uint8_t *)p_src + p_qoa->data_ofs;
+				qoa_decode_frame(ofs_src, p_qoa->frame_len, &p_qoa->desc, p_qoa->dec.ptr(), &p_qoa->dec_len);
+			}
 
-				if constexpr (sizeof(Depth) == 1) { /* conditions will not exist anymore when compiled! */
-					final <<= 8;
-					if (is_stereo) {
-						final_r <<= 8;
-					}
-				}
+			uint32_t dec_idx = pos % QOA_FRAME_LEN << (is_stereo ? 1 : 0);
 
-				if (is_stereo) {
-					next = p_src[pos + 2];
-					next_r = p_src[pos + 3];
-				} else {
-					next = p_src[pos + 1];
-				}
-
-				if constexpr (sizeof(Depth) == 1) {
-					next <<= 8;
-					if (is_stereo) {
-						next_r <<= 8;
-					}
-				}
+			final = p_qoa->dec[dec_idx];
+			if (is_stereo) {
+				final_r = p_qoa->dec[dec_idx + 1];
 			}
-			int32_t frac = int64_t(p_offset & MIX_FRAC_MASK);
 
-			final = final + ((next - final) * frac >> MIX_FRAC_BITS);
+		} else {
+			final = p_src[pos];
 			if (is_stereo) {
-				final_r = final_r + ((next_r - final_r) * frac >> MIX_FRAC_BITS);
+				final_r = p_src[pos + 1];
+			}
+			if constexpr (sizeof(Depth) == 1) { /* conditions will not exist anymore when compiled! */
+				final <<= 8;
+				if (is_stereo) {
+					final_r <<= 8;
+				}
 			}
 		}
 
@@ -266,7 +214,7 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst,
 	}
 }
 
-int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
+int AudioStreamPlaybackWAV::_mix_internal(AudioFrame *p_buffer, int p_frames) {
 	if (base->data.is_empty() || !active) {
 		for (int i = 0; i < p_frames; i++) {
 			p_buffer[i] = AudioFrame(0, 0);
@@ -274,7 +222,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
 		return 0;
 	}
 
-	int len = base->data_bytes;
+	uint32_t len = base->data_bytes;
 	switch (base->format) {
 		case AudioStreamWAV::FORMAT_8_BITS:
 			len /= 1;
@@ -294,13 +242,10 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
 		len /= 2;
 	}
 
-	/* some 64-bit fixed point precaches */
-
-	int64_t loop_begin_fp = ((int64_t)base->loop_begin << MIX_FRAC_BITS);
-	int64_t loop_end_fp = ((int64_t)base->loop_end << MIX_FRAC_BITS);
-	int64_t length_fp = ((int64_t)len << MIX_FRAC_BITS);
-	int64_t begin_limit = (base->loop_mode != AudioStreamWAV::LOOP_DISABLED) ? loop_begin_fp : 0;
-	int64_t end_limit = (base->loop_mode != AudioStreamWAV::LOOP_DISABLED) ? loop_end_fp : length_fp - MIX_FRAC_LEN;
+	int64_t loop_begin = base->loop_begin;
+	int64_t loop_end = base->loop_end;
+	int64_t begin_limit = (base->loop_mode != AudioStreamWAV::LOOP_DISABLED) ? loop_begin : 0;
+	int64_t end_limit = (base->loop_mode != AudioStreamWAV::LOOP_DISABLED) ? loop_end : len - 1;
 	bool is_stereo = base->stereo;
 
 	int32_t todo = p_frames;
@@ -309,13 +254,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
 		sign = -1;
 	}
 
-	float base_rate = AudioServer::get_singleton()->get_mix_rate();
-	float srate = base->mix_rate;
-	srate *= p_rate_scale;
-	float playback_speed_scale = AudioServer::get_singleton()->get_playback_speed_scale();
-	float fincrement = (srate * playback_speed_scale) / base_rate;
-	int32_t increment = int32_t(MAX(fincrement * MIX_FRAC_LEN, 1));
-	increment *= sign;
+	int8_t increment = sign;
 
 	//looping
 
@@ -324,13 +263,13 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
 
 	/* audio data */
 
-	const uint8_t *data = base->data.ptr() + AudioStreamWAV::DATA_PAD;
+	const uint8_t *data = base->data.ptr();
 	AudioFrame *dst_buff = p_buffer;
 
 	if (format == AudioStreamWAV::FORMAT_IMA_ADPCM) {
 		if (loop_format != AudioStreamWAV::LOOP_DISABLED) {
-			ima_adpcm[0].loop_pos = loop_begin_fp >> MIX_FRAC_BITS;
-			ima_adpcm[1].loop_pos = loop_begin_fp >> MIX_FRAC_BITS;
+			ima_adpcm[0].loop_pos = loop_begin;
+			ima_adpcm[1].loop_pos = loop_begin;
 			loop_format = AudioStreamWAV::LOOP_FORWARD;
 		}
 	}
@@ -344,16 +283,16 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
 		if (increment < 0) {
 			/* going backwards */
 
-			if (loop_format != AudioStreamWAV::LOOP_DISABLED && offset < loop_begin_fp) {
+			if (loop_format != AudioStreamWAV::LOOP_DISABLED && offset < loop_begin) {
 				/* loopstart reached */
 				if (loop_format == AudioStreamWAV::LOOP_PINGPONG) {
 					/* bounce ping pong */
-					offset = loop_begin_fp + (loop_begin_fp - offset);
+					offset = loop_begin + (loop_begin - offset);
 					increment = -increment;
 					sign *= -1;
 				} else {
 					/* go to loop-end */
-					offset = loop_end_fp - (loop_begin_fp - offset);
+					offset = loop_end - (loop_begin - offset);
 				}
 			} else {
 				/* check for sample not reaching beginning */
@@ -364,12 +303,12 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
 			}
 		} else {
 			/* going forward */
-			if (loop_format != AudioStreamWAV::LOOP_DISABLED && offset >= loop_end_fp) {
+			if (loop_format != AudioStreamWAV::LOOP_DISABLED && offset >= loop_end) {
 				/* loopend reached */
 
 				if (loop_format == AudioStreamWAV::LOOP_PINGPONG) {
 					/* bounce ping pong */
-					offset = loop_end_fp - (offset - loop_end_fp);
+					offset = loop_end - (offset - loop_end);
 					increment = -increment;
 					sign *= -1;
 				} else {
@@ -379,16 +318,16 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
 						for (int i = 0; i < 2; i++) {
 							ima_adpcm[i].step_index = ima_adpcm[i].loop_step_index;
 							ima_adpcm[i].predictor = ima_adpcm[i].loop_predictor;
-							ima_adpcm[i].last_nibble = loop_begin_fp >> MIX_FRAC_BITS;
+							ima_adpcm[i].last_nibble = loop_begin;
 						}
-						offset = loop_begin_fp;
+						offset = loop_begin;
 					} else {
-						offset = loop_begin_fp + (offset - loop_end_fp);
+						offset = loop_begin + (offset - loop_end);
 					}
 				}
 			} else {
 				/* no loop, check for end of sample */
-				if (offset >= length_fp) {
+				if (offset >= len) {
 					active = false;
 					break;
 				}
@@ -415,32 +354,32 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
 		switch (base->format) {
 			case AudioStreamWAV::FORMAT_8_BITS: {
 				if (is_stereo) {
-					do_resample<int8_t, true, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
+					decode_samples<int8_t, true, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
 				} else {
-					do_resample<int8_t, false, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
+					decode_samples<int8_t, false, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
 				}
 			} break;
 			case AudioStreamWAV::FORMAT_16_BITS: {
 				if (is_stereo) {
-					do_resample<int16_t, true, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
+					decode_samples<int16_t, true, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
 				} else {
-					do_resample<int16_t, false, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
+					decode_samples<int16_t, false, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
 				}
 
 			} break;
 			case AudioStreamWAV::FORMAT_IMA_ADPCM: {
 				if (is_stereo) {
-					do_resample<int8_t, true, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
+					decode_samples<int8_t, true, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
 				} else {
-					do_resample<int8_t, false, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
+					decode_samples<int8_t, false, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
 				}
 
 			} break;
 			case AudioStreamWAV::FORMAT_QOA: {
 				if (is_stereo) {
-					do_resample<uint8_t, true, false, true>((uint8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
+					decode_samples<uint8_t, true, false, true>((uint8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
 				} else {
-					do_resample<uint8_t, false, false, true>((uint8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
+					decode_samples<uint8_t, false, false, true>((uint8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
 				}
 			} break;
 		}
@@ -460,6 +399,10 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
 	return p_frames;
 }
 
+float AudioStreamPlaybackWAV::get_stream_sampling_rate() {
+	return base->mix_rate;
+}
+
 void AudioStreamPlaybackWAV::tag_used_streams() {
 	base->tag_used(get_playback_position());
 }
@@ -552,7 +495,7 @@ double AudioStreamWAV::get_length() const {
 			break;
 		case AudioStreamWAV::FORMAT_QOA:
 			qoa_desc desc = {};
-			qoa_decode_header(data.ptr() + DATA_PAD, data_bytes, &desc);
+			qoa_decode_header(data.ptr(), data_bytes, &desc);
 			len = desc.samples * desc.channels;
 			break;
 	}
@@ -571,28 +514,14 @@ bool AudioStreamWAV::is_monophonic() const {
 void AudioStreamWAV::set_data(const Vector<uint8_t> &p_data) {
 	AudioServer::get_singleton()->lock();
 
-	int src_data_len = p_data.size();
-
-	data.clear();
-
-	int alloc_len = src_data_len + DATA_PAD * 2;
-	data.resize(alloc_len);
-	memset(data.ptr(), 0, alloc_len);
-	memcpy(data.ptr() + DATA_PAD, p_data.ptr(), src_data_len);
-	data_bytes = src_data_len;
+	data = p_data;
+	data_bytes = p_data.size();
 
 	AudioServer::get_singleton()->unlock();
 }
 
 Vector<uint8_t> AudioStreamWAV::get_data() const {
-	Vector<uint8_t> pv;
-
-	if (data_bytes) {
-		pv.resize(data_bytes);
-		memcpy(pv.ptrw(), data.ptr() + DATA_PAD, data_bytes);
-	}
-
-	return pv;
+	return data;
 }
 
 Error AudioStreamWAV::save_to_wav(const String &p_path) {
@@ -681,7 +610,7 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() {
 	sample->base = Ref<AudioStreamWAV>(this);
 
 	if (format == AudioStreamWAV::FORMAT_QOA) {
-		uint32_t ffp = qoa_decode_header(data.ptr() + DATA_PAD, data_bytes, &sample->qoa.desc);
+		uint32_t ffp = qoa_decode_header(data.ptr(), data_bytes, &sample->qoa.desc);
 		ERR_FAIL_COND_V(ffp != 8, Ref<AudioStreamPlaybackWAV>());
 		sample->qoa.frame_len = qoa_max_frame_size(&sample->qoa.desc);
 		int samples_len = (sample->qoa.desc.samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc.samples);
diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h
index e7cd366eeefe..41055ed9e715 100644
--- a/scene/resources/audio_stream_wav.h
+++ b/scene/resources/audio_stream_wav.h
@@ -36,13 +36,8 @@
 
 class AudioStreamWAV;
 
-class AudioStreamPlaybackWAV : public AudioStreamPlayback {
-	GDCLASS(AudioStreamPlaybackWAV, AudioStreamPlayback);
-	enum {
-		MIX_FRAC_BITS = 13,
-		MIX_FRAC_LEN = (1 << MIX_FRAC_BITS),
-		MIX_FRAC_MASK = MIX_FRAC_LEN - 1,
-	};
+class AudioStreamPlaybackWAV : public AudioStreamPlaybackResampled {
+	GDCLASS(AudioStreamPlaybackWAV, AudioStreamPlaybackResampled);
 
 	struct IMA_ADPCM_State {
 		int16_t step_index = 0;
@@ -61,23 +56,24 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback {
 		uint32_t frame_len = 0;
 		LocalVector<int16_t> dec;
 		uint32_t dec_len = 0;
-		int64_t cache_pos = -1;
-		int16_t cache[2] = { 0, 0 };
-		int16_t cache_r[2] = { 0, 0 };
 	} qoa;
 
 	int64_t offset = 0;
-	int sign = 1;
+	int8_t sign = 1;
 	bool active = false;
 	friend class AudioStreamWAV;
 	Ref<AudioStreamWAV> base;
 
 	template <typename Depth, bool is_stereo, bool is_ima_adpcm, bool is_qoa>
-	void do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm, QOA_State *p_qoa);
+	void decode_samples(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int8_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm, QOA_State *p_qoa);
 
 	bool _is_sample = false;
 	Ref<AudioSamplePlayback> sample_playback;
 
+protected:
+	virtual int _mix_internal(AudioFrame *p_buffer, int p_frames) override;
+	virtual float get_stream_sampling_rate() override;
+
 public:
 	virtual void start(double p_from_pos = 0.0) override;
 	virtual void stop() override;
@@ -88,8 +84,6 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback {
 	virtual double get_playback_position() const override;
 	virtual void seek(double p_time) override;
 
-	virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
-
 	virtual void tag_used_streams() override;
 
 	virtual void set_is_sample(bool p_is_sample) override;
@@ -124,10 +118,6 @@ class AudioStreamWAV : public AudioStream {
 private:
 	friend class AudioStreamPlaybackWAV;
 
-	enum {
-		DATA_PAD = 16 //padding for interpolation
-	};
-
 	Format format = FORMAT_8_BITS;
 	LoopMode loop_mode = LOOP_DISABLED;
 	bool stereo = false;


Test patch: