Explorar o código

fix for stereo panning adding _calc_output_vol_stereo issue 103989

Julian Todd hai 3 meses
pai
achega
daf8eee9cb

+ 3 - 1
doc/classes/AudioStreamPlayer3D.xml

@@ -94,7 +94,9 @@
 			The maximum number of sounds this node can play at the same time. Playing additional sounds after this value is reached will cut off the oldest sounds.
 			The maximum number of sounds this node can play at the same time. Playing additional sounds after this value is reached will cut off the oldest sounds.
 		</member>
 		</member>
 		<member name="panning_strength" type="float" setter="set_panning_strength" getter="get_panning_strength" default="1.0">
 		<member name="panning_strength" type="float" setter="set_panning_strength" getter="get_panning_strength" default="1.0">
-			Scales the panning strength for this node by multiplying the base [member ProjectSettings.audio/general/3d_panning_strength] with this factor. Higher values will pan audio from left to right more dramatically than lower values.
+			Scales the panning strength for this node by multiplying the base [member ProjectSettings.audio/general/3d_panning_strength] by this factor. If the product is [code]0.0[/code] then stereo panning is disabled and the volume is the same for all channels. If the product is [code]1.0[/code] then one of the channels will be muted when the sound is located exactly to the left (or right) of the listener.
+			Two speaker stereo arrangements implement the [url=https://webaudio.github.io/web-audio-api/#stereopanner-algorithm]WebAudio standard for StereoPannerNode Panning[/url] where the volume is cosine of half the azimuth angle to the ear.
+			For other speaker arrangements such as the 5.1 and 7.1 the SPCAP (Speaker-Placement Correction Amplitude) algorithm is implemented.
 		</member>
 		</member>
 		<member name="pitch_scale" type="float" setter="set_pitch_scale" getter="get_pitch_scale" default="1.0">
 		<member name="pitch_scale" type="float" setter="set_pitch_scale" getter="get_pitch_scale" default="1.0">
 			The pitch and the tempo of the audio, as a multiplier of the audio sample's sample rate.
 			The pitch and the tempo of the audio, as a multiplier of the audio sample's sample rate.

+ 1 - 1
doc/classes/ProjectSettings.xml

@@ -418,7 +418,7 @@
 		</member>
 		</member>
 		<member name="audio/general/3d_panning_strength" type="float" setter="" getter="" default="0.5">
 		<member name="audio/general/3d_panning_strength" type="float" setter="" getter="" default="0.5">
 			The base strength of the panning effect for all [AudioStreamPlayer3D] nodes. The panning strength can be further scaled on each Node using [member AudioStreamPlayer3D.panning_strength]. A value of [code]0.0[/code] disables stereo panning entirely, leaving only volume attenuation in place. A value of [code]1.0[/code] completely mutes one of the channels if the sound is located exactly to the left (or right) of the listener.
 			The base strength of the panning effect for all [AudioStreamPlayer3D] nodes. The panning strength can be further scaled on each Node using [member AudioStreamPlayer3D.panning_strength]. A value of [code]0.0[/code] disables stereo panning entirely, leaving only volume attenuation in place. A value of [code]1.0[/code] completely mutes one of the channels if the sound is located exactly to the left (or right) of the listener.
-			The default value of [code]0.5[/code] is tuned for headphones. When using speakers, you may find lower values to sound better as speakers have a lower stereo separation compared to headphones.
+			The default value of [code]0.5[/code] is tuned for headphones which means that the opposite side channel goes no lower than 50% of the volume of the nearside channel. You may find that you can set this value higher for speakers to have the same effect since both ears can hear from each speaker.
 		</member>
 		</member>
 		<member name="audio/general/default_playback_type" type="int" setter="" getter="" default="0" experimental="">
 		<member name="audio/general/default_playback_type" type="int" setter="" getter="" default="0" experimental="">
 			Specifies the default playback type of the platform.
 			Specifies the default playback type of the platform.

+ 26 - 4
scene/3d/audio_stream_player_3d.cpp

@@ -63,6 +63,8 @@ public:
 			w[speaker_num].direction = speaker_directions[speaker_num];
 			w[speaker_num].direction = speaker_directions[speaker_num];
 			w[speaker_num].squared_gain = 0.0;
 			w[speaker_num].squared_gain = 0.0;
 			w[speaker_num].effective_number_of_speakers = 0.0;
 			w[speaker_num].effective_number_of_speakers = 0.0;
+		}
+		for (unsigned int speaker_num = 0; speaker_num < speaker_count; speaker_num++) {
 			for (unsigned int other_speaker_num = 0; other_speaker_num < speaker_count; other_speaker_num++) {
 			for (unsigned int other_speaker_num = 0; other_speaker_num < speaker_count; other_speaker_num++) {
 				w[speaker_num].effective_number_of_speakers += 0.5 * (1.0 + w[speaker_num].direction.dot(w[other_speaker_num].direction));
 				w[speaker_num].effective_number_of_speakers += 0.5 * (1.0 + w[speaker_num].direction.dot(w[other_speaker_num].direction));
 			}
 			}
@@ -144,6 +146,18 @@ void AudioStreamPlayer3D::_calc_output_vol(const Vector3 &source_dir, real_t tig
 	}
 	}
 }
 }
 
 
+// Set the volume to cosine of half horizontal the angle from the source to the left/right speaker direction ignoring elevation.
+// Then scale `cosx` so that greatest ratio of the speaker volumes is `1-panning_strength`.
+// See https://github.com/godotengine/godot/issues/103989 for evidence that this is the most standard implementation.
+AudioFrame AudioStreamPlayer3D::_calc_output_vol_stereo(const Vector3 &source_dir, real_t panning_strength) {
+	double flatrad = sqrt(source_dir.x * source_dir.x + source_dir.z * source_dir.z);
+	double g = CLAMP((1.0 - panning_strength) * (1.0 - panning_strength), 0.0, 1.0);
+	double f = (1.0 - g) / (1.0 + g);
+	double cosx = CLAMP(source_dir.x / (flatrad == 0.0 ? 1.0 : flatrad), -1.0, 1.0);
+	double fcosx = cosx * f;
+	return AudioFrame(sqrt((-fcosx + 1.0) / 2.0), sqrt((fcosx + 1.0) / 2.0));
+}
+
 #ifndef PHYSICS_3D_DISABLED
 #ifndef PHYSICS_3D_DISABLED
 void AudioStreamPlayer3D::_calc_reverb_vol(Area3D *area, Vector3 listener_area_pos, Vector<AudioFrame> direct_path_vol, Vector<AudioFrame> &reverb_vol) {
 void AudioStreamPlayer3D::_calc_reverb_vol(Area3D *area, Vector3 listener_area_pos, Vector<AudioFrame> direct_path_vol, Vector<AudioFrame> &reverb_vol) {
 	reverb_vol.resize(4);
 	reverb_vol.resize(4);
@@ -449,10 +463,18 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() {
 		for (Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) {
 		for (Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) {
 			AudioServer::get_singleton()->set_playback_highshelf_params(playback, linear_attenuation, attenuation_filter_cutoff_hz);
 			AudioServer::get_singleton()->set_playback_highshelf_params(playback, linear_attenuation, attenuation_filter_cutoff_hz);
 		}
 		}
-		// Bake in a constant factor here to allow the project setting defaults for 2d and 3d to be normalized to 1.0.
-		float tightness = cached_global_panning_strength * 2.0f;
-		tightness *= panning_strength;
-		_calc_output_vol(local_pos.normalized(), tightness, output_volume_vector);
+
+		if (AudioServer::get_singleton()->get_speaker_mode() == AudioServer::SPEAKER_MODE_STEREO) {
+			output_volume_vector.write[0] = _calc_output_vol_stereo(local_pos, cached_global_panning_strength * panning_strength);
+			output_volume_vector.write[1] = AudioFrame(0, 0);
+			output_volume_vector.write[2] = AudioFrame(0, 0);
+			output_volume_vector.write[3] = AudioFrame(0, 0);
+		} else {
+			// Bake in a constant factor here to allow the project setting defaults for 2d and 3d to be normalized to 1.0.
+			float tightness = cached_global_panning_strength * 2.0f;
+			tightness *= panning_strength;
+			_calc_output_vol(local_pos.normalized(), tightness, output_volume_vector);
+		}
 
 
 		for (unsigned int k = 0; k < 4; k++) {
 		for (unsigned int k = 0; k < 4; k++) {
 			output_volume_vector.write[k] = multiplier * output_volume_vector[k];
 			output_volume_vector.write[k] = multiplier * output_volume_vector[k];

+ 1 - 0
scene/3d/audio_stream_player_3d.h

@@ -81,6 +81,7 @@ private:
 	bool force_update_panning = false;
 	bool force_update_panning = false;
 
 
 	static void _calc_output_vol(const Vector3 &source_dir, real_t tightness, Vector<AudioFrame> &output);
 	static void _calc_output_vol(const Vector3 &source_dir, real_t tightness, Vector<AudioFrame> &output);
+	static AudioFrame _calc_output_vol_stereo(const Vector3 &source_dir, real_t panning_strength);
 
 
 #ifndef PHYSICS_3D_DISABLED
 #ifndef PHYSICS_3D_DISABLED
 	void _calc_reverb_vol(Area3D *area, Vector3 listener_area_pos, Vector<AudioFrame> direct_path_vol, Vector<AudioFrame> &reverb_vol);
 	void _calc_reverb_vol(Area3D *area, Vector3 listener_area_pos, Vector<AudioFrame> direct_path_vol, Vector<AudioFrame> &reverb_vol);