1
0
mirror of https://github.com/godotengine/godot synced 2024-07-01 08:39:18 +00:00

Add samples playback support

This commit is contained in:
Adam Scott 2024-04-18 10:50:34 -04:00
parent eb20a68b32
commit 52fa4f05f3
No known key found for this signature in database
GPG Key ID: F6BA2A0302E21A77
42 changed files with 2611 additions and 46 deletions

View File

@ -1480,6 +1480,8 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("animation/warnings/check_angle_interpolation_type_conflicting", true);
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "audio/buses/default_bus_layout", PROPERTY_HINT_FILE, "*.tres"), "res://default_bus_layout.tres");
GLOBAL_DEF(PropertyInfo(Variant::INT, "audio/general/default_playback_type", PROPERTY_HINT_ENUM, "Stream,Sample"), 0);
GLOBAL_DEF(PropertyInfo(Variant::INT, "audio/general/default_playback_type.web", PROPERTY_HINT_ENUM, "Stream,Sample"), 1);
GLOBAL_DEF_RST("audio/general/text_to_speech", false);
GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/general/2d_panning_strength", PROPERTY_HINT_RANGE, "0,2,0.01"), 0.5f);
GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/general/3d_panning_strength", PROPERTY_HINT_RANGE, "0,2,0.01"), 0.5f);

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="AudioSample" inherits="RefCounted" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Base class for audio samples.
</brief_description>
<description>
Base class for audio samples.
</description>
<tutorials>
</tutorials>
</class>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="AudioSamplePlayback" inherits="RefCounted" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Meta class for playing back audio samples.
</brief_description>
<description>
Meta class for playing back audio samples.
</description>
<tutorials>
</tutorials>
</class>

View File

@ -183,6 +183,14 @@
If [code]true[/code], the bus at index [param bus_idx] is in solo mode.
</description>
</method>
<method name="is_stream_registered_as_sample" experimental="">
<return type="bool" />
<param index="0" name="stream" type="AudioStream" />
<description>
If [code]true[/code], the stream is registered as a sample. The engine will not have to register it before playing the sample.
If [code]false[/code], the stream will have to be registered before playing it. To prevent lag spikes, register the stream as sample with [method register_stream_as_sample].
</description>
</method>
<method name="lock">
<return type="void" />
<description>
@ -198,6 +206,14 @@
Moves the bus from index [param index] to index [param to_index].
</description>
</method>
<method name="register_stream_as_sample" experimental="">
<return type="void" />
<param index="0" name="stream" type="AudioStream" />
<description>
Forces the registration of a stream as a sample.
[b]Note:[/b] Lag spikes may occur when calling this method, especially on single-threaded builds. It is suggested to call this method while loading assets, where the lag spike could be masked, instead of registering the sample right before it needs to be played.
</description>
</method>
<method name="remove_bus">
<return type="void" />
<param index="0" name="index" type="int" />
@ -344,5 +360,19 @@
<constant name="SPEAKER_SURROUND_71" value="3" enum="SpeakerMode">
A 7.1 channel surround setup was detected.
</constant>
<constant name="PLAYBACK_TYPE_DEFAULT" value="0" enum="PlaybackType" experimental="">
The playback will be considered of the type declared at [member ProjectSettings.audio/general/default_playback_type].
</constant>
<constant name="PLAYBACK_TYPE_STREAM" value="1" enum="PlaybackType" experimental="">
Force the playback to be considered as a stream.
</constant>
<constant name="PLAYBACK_TYPE_SAMPLE" value="2" enum="PlaybackType" experimental="">
Force the playback to be considered as a sample. This can provide lower latency and more stable playback (with less risk of audio crackling), at the cost of having less flexibility.
[b]Note:[/b] Only currently supported on the web platform.
[b]Note:[/b] [AudioEffect]s are not supported when playback is considered as a sample.
</constant>
<constant name="PLAYBACK_TYPE_MAX" value="3" enum="PlaybackType" experimental="">
Represents the size of the [enum PlaybackType] enum.
</constant>
</constants>
</class>

View File

@ -57,6 +57,18 @@
Override this method to customize the returned value of [method is_monophonic]. Should return [code]true[/code] if this audio stream only supports one channel.
</description>
</method>
<method name="can_be_sampled" qualifiers="const" experimental="">
<return type="bool" />
<description>
Returns if the current [AudioStream] can be used as a sample. Only static streams can be sampled.
</description>
</method>
<method name="generate_sample" qualifiers="const" experimental="">
<return type="AudioSample" />
<description>
Generates an [AudioSample] based on the current stream.
</description>
</method>
<method name="get_length" qualifiers="const">
<return type="float" />
<description>
@ -69,6 +81,12 @@
Returns a newly created [AudioStreamPlayback] intended to play this audio stream. Useful for when you want to extend [method _instantiate_playback] but call [method instantiate_playback] from an internally held AudioStream subresource. An example of this can be found in the source code for [code]AudioStreamRandomPitch::instantiate_playback[/code].
</description>
</method>
<method name="is_meta_stream" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the stream is a collection of other streams, [code]false[/code] otherwise.
</description>
</method>
<method name="is_monophonic" qualifiers="const">
<return type="bool" />
<description>

View File

@ -79,5 +79,18 @@
Overridable method. Called whenever the audio stream is mixed if the playback is active and [method AudioServer.set_enable_tagging_used_audio_streams] has been set to [code]true[/code]. Editor plugins may use this method to "tag" the current position along the audio stream and display it in a preview.
</description>
</method>
<method name="get_sample_playback" qualifiers="const" experimental="">
<return type="AudioSamplePlayback" />
<description>
Returns the [AudioSamplePlayback] associated with this [AudioStreamPlayback] for playing back the audio sample of this stream.
</description>
</method>
<method name="set_sample_playback" experimental="">
<return type="void" />
<param index="0" name="playback_sample" type="AudioSamplePlayback" />
<description>
Associates [AudioSamplePlayback] to this [AudioStreamPlayback] for playing back the audio sample of this stream.
</description>
</method>
</methods>
</class>

View File

@ -22,8 +22,10 @@
<param index="1" name="from_offset" type="float" default="0" />
<param index="2" name="volume_db" type="float" default="0" />
<param index="3" name="pitch_scale" type="float" default="1.0" />
<param index="4" name="playback_type" type="int" enum="AudioServer.PlaybackType" default="0" />
<param index="5" name="bus" type="StringName" default="&amp;&quot;Master&quot;" />
<description>
Play an [AudioStream] at a given offset, volume and pitch scale. Playback starts immediately.
Play an [AudioStream] at a given offset, volume, pitch scale, playback type, and bus. Playback starts immediately.
The return value is a unique integer ID that is associated to this playback stream and which can be used to control it.
This ID becomes invalid when the stream ends (if it does not loop), when the [AudioStreamPlaybackPolyphonic] is stopped, or when [method stop_stream] is called.
This function returns [constant INVALID_ID] if the amount of streams currently playing equals [member AudioStreamPolyphonic.polyphony]. If you need a higher amount of maximum polyphony, raise this value.

View File

@ -74,6 +74,9 @@
<member name="pitch_scale" type="float" setter="set_pitch_scale" getter="get_pitch_scale" default="1.0">
The audio's pitch and tempo, as a multiplier of the [member stream]'s sample rate. A value of [code]2.0[/code] doubles the audio's pitch, while a value of [code]0.5[/code] halves the pitch.
</member>
<member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental="">
The playback type of the stream player. If set other than to the default value, it will force that playback type.
</member>
<member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false">
If [code]true[/code], this node is playing sounds. Setting this property has the same effect as [method play] and [method stop].
</member>

View File

@ -78,6 +78,9 @@
<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.
</member>
<member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental="">
The playback type of the stream player. If set other than to the default value, it will force that playback type.
</member>
<member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false">
If [code]true[/code], audio is playing or is queued to be played (see [method play]).
</member>

View File

@ -99,6 +99,9 @@
<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.
</member>
<member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental="">
The playback type of the stream player. If set other than to the default value, it will force that playback type.
</member>
<member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false">
If [code]true[/code], audio is playing or is queued to be played (see [method play]).
</member>

View File

@ -393,7 +393,7 @@
Safer override for [member audio/driver/mix_rate] in the Web platform. Here [code]0[/code] means "let the browser choose" (since some browsers do not like forcing the mix rate).
</member>
<member name="audio/driver/output_latency" type="int" setter="" getter="" default="15">
Specifies the preferred output latency in milliseconds for audio. Lower values will result in lower audio latency at the cost of increased CPU usage. Low values may result in audible cracking on slower hardware.
Specifies the preferred output latency in milliseconds for audio. Lower values will result in lower audio latency at the cost of increased CPU usage. Low values may result in audible crackling on slower hardware.
Audio output latency may be constrained by the host operating system and audio hardware drivers. If the host can not provide the specified audio output latency then Godot will attempt to use the nearest latency allowed by the host. As such you should always use [method AudioServer.get_output_latency] to determine the actual audio output latency.
Audio output latency can be overridden using the [code]--audio-output-latency &lt;ms&gt;[/code] command line argument.
[b]Note:[/b] This setting is ignored on Android, and on all versions of Windows prior to Windows 10.
@ -409,6 +409,15 @@
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.
</member>
<member name="audio/general/default_playback_type" type="int" setter="" getter="" default="0" experimental="">
Specifies the default playback type of the platform.
The default value is set to [b]Stream[/b], as most platforms have no issues mixing streams.
</member>
<member name="audio/general/default_playback_type.web" type="int" setter="" getter="" default="1" experimental="">
Specifies the default playback type of the Web platform.
The default value is set to [b]Sample[/b] as the Web platform is not suited to mix audio streams outside of the Web Audio API, especially when exporting a single-threaded game. [b]Sample[/b] allows for lower latency on the web platform at the cost of flexibility ([AudioEffect]s are not supported).
[b]Warning:[/b] Forcing [b]Stream[/b] on the Web platform may cause high audio latency and crackling, especially when exporting a multi-threaded game.
</member>
<member name="audio/general/ios/mix_with_others" type="bool" setter="" getter="" default="false">
Sets the [url=https://developer.apple.com/documentation/avfaudio/avaudiosession/categoryoptions/1616611-mixwithothers]mixWithOthers[/url] option for the AVAudioSession on iOS. This will override the mix behavior, if the category is set to [code]Play and Record[/code], [code]Playback[/code], or [code]Multi Route[/code].
[code]Ambient[/code] always has this set per default.

View File

@ -365,3 +365,10 @@ Validate extension JSON: Error: Field 'classes/Animation/methods/track_find_key/
Added optional arguments to avoid finding keys out of the animation range (GH-86661), and to handle backward seeking.
Compatibility method registered.
GH-91382
--------
Validate extension JSON: Error: Field 'classes/AudioStreamPlaybackPolyphonic/methods/play_stream/arguments': size changed value in new API, from 4 to 6.
Optional arguments added. Compatibility methods registered.

View File

@ -145,6 +145,22 @@ void AudioStreamPlaybackMP3::tag_used_streams() {
mp3_stream->tag_used(get_playback_position());
}
void AudioStreamPlaybackMP3::set_is_sample(bool p_is_sample) {
_is_sample = p_is_sample;
}
bool AudioStreamPlaybackMP3::get_is_sample() const {
return _is_sample;
}
Ref<AudioSamplePlayback> AudioStreamPlaybackMP3::get_sample_playback() const {
return sample_playback;
}
void AudioStreamPlaybackMP3::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
sample_playback = p_playback;
}
void AudioStreamPlaybackMP3::set_parameter(const StringName &p_name, const Variant &p_value) {
if (p_name == SNAME("looping")) {
if (p_value == Variant()) {
@ -287,6 +303,18 @@ int AudioStreamMP3::get_bar_beats() const {
return bar_beats;
}
Ref<AudioSample> AudioStreamMP3::generate_sample() const {
Ref<AudioSample> sample;
sample.instantiate();
sample->stream = this;
sample->loop_mode = loop
? AudioSample::LoopMode::LOOP_FORWARD
: AudioSample::LoopMode::LOOP_DISABLED;
sample->loop_begin = loop_offset;
sample->loop_end = 0;
return sample;
}
void AudioStreamMP3::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamMP3::set_data);
ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamMP3::get_data);

View File

@ -58,6 +58,9 @@ class AudioStreamPlaybackMP3 : public AudioStreamPlaybackResampled {
Ref<AudioStreamMP3> mp3_stream;
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;
@ -74,6 +77,11 @@ public:
virtual void tag_used_streams() override;
virtual void set_is_sample(bool p_is_sample) override;
virtual bool get_is_sample() const override;
virtual Ref<AudioSamplePlayback> get_sample_playback() const override;
virtual void set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) override;
virtual void set_parameter(const StringName &p_name, const Variant &p_value) override;
virtual Variant get_parameter(const StringName &p_name) const override;
@ -131,6 +139,11 @@ public:
virtual bool is_monophonic() const override;
virtual bool can_be_sampled() const override {
return true;
}
virtual Ref<AudioSample> generate_sample() const override;
virtual void get_parameter_list(List<Parameter> *r_parameters) override;
AudioStreamMP3();

View File

@ -376,6 +376,22 @@ void AudioStreamPlaybackOggVorbis::seek(double p_time) {
}
}
void AudioStreamPlaybackOggVorbis::set_is_sample(bool p_is_sample) {
_is_sample = p_is_sample;
}
bool AudioStreamPlaybackOggVorbis::get_is_sample() const {
return _is_sample;
}
Ref<AudioSamplePlayback> AudioStreamPlaybackOggVorbis::get_sample_playback() const {
return sample_playback;
}
void AudioStreamPlaybackOggVorbis::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
sample_playback = p_playback;
}
AudioStreamPlaybackOggVorbis::~AudioStreamPlaybackOggVorbis() {
if (block_is_allocated) {
vorbis_block_clear(&block);
@ -517,6 +533,18 @@ void AudioStreamOggVorbis::get_parameter_list(List<Parameter> *r_parameters) {
r_parameters->push_back(Parameter(PropertyInfo(Variant::BOOL, "looping", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_CHECKABLE), Variant()));
}
Ref<AudioSample> AudioStreamOggVorbis::generate_sample() const {
Ref<AudioSample> sample;
sample.instantiate();
sample->stream = this;
sample->loop_mode = loop
? AudioSample::LoopMode::LOOP_FORWARD
: AudioSample::LoopMode::LOOP_DISABLED;
sample->loop_begin = loop_offset;
sample->loop_end = 0;
return sample;
}
void AudioStreamOggVorbis::_bind_methods() {
ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_buffer", "buffer"), &AudioStreamOggVorbis::load_from_buffer);
ClassDB::bind_static_method("AudioStreamOggVorbis", D_METHOD("load_from_file", "path"), &AudioStreamOggVorbis::load_from_file);

View File

@ -75,6 +75,9 @@ class AudioStreamPlaybackOggVorbis : public AudioStreamPlaybackResampled {
Ref<OggPacketSequencePlayback> vorbis_data_playback;
Ref<AudioStreamOggVorbis> vorbis_stream;
bool _is_sample = false;
Ref<AudioSamplePlayback> sample_playback;
int _mix_frames(AudioFrame *p_buffer, int p_frames);
int _mix_frames_vorbis(AudioFrame *p_buffer, int p_frames);
@ -100,6 +103,11 @@ public:
virtual void set_parameter(const StringName &p_name, const Variant &p_value) override;
virtual Variant get_parameter(const StringName &p_name) const override;
virtual void set_is_sample(bool p_is_sample) override;
virtual bool get_is_sample() const override;
virtual Ref<AudioSamplePlayback> get_sample_playback() const override;
virtual void set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) override;
AudioStreamPlaybackOggVorbis() {}
~AudioStreamPlaybackOggVorbis();
};
@ -159,6 +167,11 @@ public:
virtual void get_parameter_list(List<Parameter> *r_parameters) override;
virtual bool can_be_sampled() const override {
return true;
}
virtual Ref<AudioSample> generate_sample() const override;
AudioStreamOggVorbis();
virtual ~AudioStreamOggVorbis();
};

View File

@ -33,6 +33,7 @@
#include "godot_audio.h"
#include "core/config/project_settings.h"
#include "servers/audio/audio_stream.h"
#include <emscripten.h>
@ -186,6 +187,181 @@ Error AudioDriverWeb::input_stop() {
return OK;
}
bool AudioDriverWeb::is_stream_registered_as_sample(const Ref<AudioStream> &p_stream) const {
ERR_FAIL_COND_V_MSG(p_stream.is_null(), false, "Parameter p_stream is null.");
return godot_audio_sample_stream_is_registered(itos(p_stream->get_instance_id()).utf8().get_data()) != 0;
}
void AudioDriverWeb::register_sample(const Ref<AudioSample> &p_sample) {
ERR_FAIL_COND_MSG(p_sample.is_null(), "Parameter p_sample is null.");
ERR_FAIL_COND_MSG(p_sample->stream.is_null(), "Parameter p_sample->stream is null.");
String loop_mode;
switch (p_sample->loop_mode) {
case AudioSample::LoopMode::LOOP_DISABLED: {
loop_mode = "disabled";
} break;
case AudioSample::LoopMode::LOOP_FORWARD: {
loop_mode = "forward";
} break;
case AudioSample::LoopMode::LOOP_PINGPONG: {
loop_mode = "pingpong";
} break;
case AudioSample::LoopMode::LOOP_BACKWARD: {
loop_mode = "backward";
} break;
}
double length = p_sample->stream->get_length();
Vector<AudioFrame> frames;
int frames_total = mix_rate * length;
{
Ref<AudioStreamPlayback> stream_playback = p_sample->stream->instantiate_playback();
frames.resize(frames_total);
AudioFrame *frames_ptr = frames.ptrw();
stream_playback->start();
stream_playback->mix(frames_ptr, 1.0f, frames_total);
}
PackedFloat32Array data;
data.resize(frames_total * 2);
float *data_ptrw = data.ptrw();
for (int i = 0; i < frames_total; i++) {
data_ptrw[i] = frames[i].left;
data_ptrw[i + frames_total] = frames[i].right;
}
godot_audio_sample_register_stream(
itos(p_sample->stream->get_instance_id()).utf8().get_data(),
data_ptrw,
frames_total,
loop_mode.utf8().get_data(),
p_sample->loop_begin,
p_sample->loop_end);
}
void AudioDriverWeb::unregister_sample(const Ref<AudioSample> &p_sample) {
ERR_FAIL_COND_MSG(p_sample.is_null(), "Parameter p_sample is null.");
ERR_FAIL_COND_MSG(p_sample->stream.is_null(), "Parameter p_sample->stream is null.");
godot_audio_sample_unregister_stream(itos(p_sample->stream->get_instance_id()).utf8().get_data());
}
void AudioDriverWeb::start_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null.");
ERR_FAIL_COND_MSG(p_playback->stream.is_null(), "Parameter p_playback->stream is null.");
constexpr int real_max_channels = AudioServer::MAX_CHANNELS_PER_BUS * 2;
PackedFloat32Array volume;
volume.resize(real_max_channels);
float *volume_ptrw = volume.ptrw();
for (int i = 0; i < real_max_channels; i += 2) {
if (p_playback->volume_vector.is_empty()) {
volume_ptrw[i] = 0;
volume_ptrw[i + 1] = 0;
} else {
const AudioFrame &frame = p_playback->volume_vector[i / 2];
volume_ptrw[i] = frame.left;
volume_ptrw[i + 1] = frame.right;
}
}
godot_audio_sample_start(
itos(p_playback->get_instance_id()).utf8().get_data(),
itos(p_playback->stream->get_instance_id()).utf8().get_data(),
AudioServer::get_singleton()->get_bus_index(p_playback->bus),
p_playback->offset,
volume_ptrw);
}
void AudioDriverWeb::stop_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null.");
godot_audio_sample_stop(itos(p_playback->get_instance_id()).utf8().get_data());
}
void AudioDriverWeb::set_sample_playback_pause(const Ref<AudioSamplePlayback> &p_playback, bool p_paused) {
ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null.");
godot_audio_sample_set_pause(itos(p_playback->get_instance_id()).utf8().get_data(), p_paused);
}
bool AudioDriverWeb::is_sample_playback_active(const Ref<AudioSamplePlayback> &p_playback) {
ERR_FAIL_COND_V_MSG(p_playback.is_null(), false, "Parameter p_playback is null.");
return godot_audio_sample_is_active(itos(p_playback->get_instance_id()).utf8().get_data()) != 0;
}
void AudioDriverWeb::update_sample_playback_pitch_scale(const Ref<AudioSamplePlayback> &p_playback, float p_pitch_scale) {
ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null.");
godot_audio_sample_update_pitch_scale(
itos(p_playback->get_instance_id()).utf8().get_data(),
p_pitch_scale);
}
void AudioDriverWeb::set_sample_playback_bus_volumes_linear(const Ref<AudioSamplePlayback> &p_playback, const HashMap<StringName, Vector<AudioFrame>> &p_bus_volumes) {
ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null.");
constexpr int real_max_channels = AudioServer::MAX_CHANNELS_PER_BUS * 2;
PackedInt32Array buses;
buses.resize(p_bus_volumes.size());
int32_t *buses_ptrw = buses.ptrw();
PackedFloat32Array values;
values.resize(p_bus_volumes.size() * AudioServer::MAX_CHANNELS_PER_BUS * 2);
float *values_ptrw = values.ptrw();
int idx = 0;
for (KeyValue<StringName, Vector<AudioFrame>> pair : p_bus_volumes) {
int bus_index = AudioServer::get_singleton()->get_bus_index(pair.key);
buses_ptrw[idx] = bus_index;
ERR_FAIL_COND(pair.value.size() != AudioServer::MAX_CHANNELS_PER_BUS);
for (int i = 0; i < real_max_channels; i += 2) {
const AudioFrame &frame = pair.value[i / 2];
values_ptrw[(idx * real_max_channels) + i] = frame.left;
values_ptrw[(idx * real_max_channels) + i + 1] = frame.right;
}
idx++;
}
godot_audio_sample_set_volumes_linear(
itos(p_playback->get_instance_id()).utf8().get_data(),
buses_ptrw,
buses.size(),
values_ptrw,
values.size());
}
void AudioDriverWeb::set_sample_bus_count(int p_count) {
godot_audio_sample_bus_set_count(p_count);
}
void AudioDriverWeb::remove_sample_bus(int p_index) {
godot_audio_sample_bus_remove(p_index);
}
void AudioDriverWeb::add_sample_bus(int p_at_pos) {
godot_audio_sample_bus_add(p_at_pos);
}
void AudioDriverWeb::move_sample_bus(int p_bus, int p_to_pos) {
godot_audio_sample_bus_move(p_bus, p_to_pos);
}
void AudioDriverWeb::set_sample_bus_send(int p_bus, const StringName &p_send) {
godot_audio_sample_bus_set_send(p_bus, AudioServer::get_singleton()->get_bus_index(p_send));
}
void AudioDriverWeb::set_sample_bus_volume_db(int p_bus, float p_volume_db) {
godot_audio_sample_bus_set_volume_db(p_bus, p_volume_db);
}
void AudioDriverWeb::set_sample_bus_solo(int p_bus, bool p_enable) {
godot_audio_sample_bus_set_solo(p_bus, p_enable);
}
void AudioDriverWeb::set_sample_bus_mute(int p_bus, bool p_enable) {
godot_audio_sample_bus_set_mute(p_bus, p_enable);
}
#ifdef THREADS_ENABLED
/// AudioWorkletNode implementation (threads)

View File

@ -87,6 +87,26 @@ public:
static void resume();
// Samples.
virtual bool is_stream_registered_as_sample(const Ref<AudioStream> &p_stream) const override;
virtual void register_sample(const Ref<AudioSample> &p_sample) override;
virtual void unregister_sample(const Ref<AudioSample> &p_sample) override;
virtual void start_sample_playback(const Ref<AudioSamplePlayback> &p_playback) override;
virtual void stop_sample_playback(const Ref<AudioSamplePlayback> &p_playback) override;
virtual void set_sample_playback_pause(const Ref<AudioSamplePlayback> &p_playback, bool p_paused) override;
virtual bool is_sample_playback_active(const Ref<AudioSamplePlayback> &p_playback) override;
virtual void update_sample_playback_pitch_scale(const Ref<AudioSamplePlayback> &p_playback, float p_pitch_scale = 0.0f) override;
virtual void set_sample_playback_bus_volumes_linear(const Ref<AudioSamplePlayback> &p_playback, const HashMap<StringName, Vector<AudioFrame>> &p_bus_volumes) override;
virtual void set_sample_bus_count(int p_count) override;
virtual void remove_sample_bus(int p_index) override;
virtual void add_sample_bus(int p_at_pos = -1) override;
virtual void move_sample_bus(int p_bus, int p_to_pos) override;
virtual void set_sample_bus_send(int p_bus, const StringName &p_send) override;
virtual void set_sample_bus_volume_db(int p_bus, float p_volume_db) override;
virtual void set_sample_bus_solo(int p_bus, bool p_enable) override;
virtual void set_sample_bus_mute(int p_bus, bool p_enable) override;
AudioDriverWeb() {}
};

View File

@ -147,6 +147,7 @@ module.exports = [
'GodotEventListeners': true,
'GodotFS': true,
'GodotOS': true,
'GodotAudio': true,
'GodotRuntime': true,
'IDHandler': true,
'XRWebGLLayer': true,

View File

@ -47,6 +47,26 @@ extern void godot_audio_resume();
extern int godot_audio_input_start();
extern void godot_audio_input_stop();
// Samples
extern int godot_audio_sample_stream_is_registered(const char *p_stream_object_id);
extern void godot_audio_sample_register_stream(const char *p_stream_object_id, float *p_frames_buf, int p_frames_total, const char *p_loop_mode, int p_loop_begin, int p_loop_end);
extern void godot_audio_sample_unregister_stream(const char *p_stream_object_id);
extern void godot_audio_sample_start(const char *p_playback_object_id, const char *p_stream_object_id, int p_bus_index, float p_offset, float *p_volume_ptr);
extern void godot_audio_sample_stop(const char *p_playback_object_id);
extern void godot_audio_sample_set_pause(const char *p_playback_object_id, bool p_pause);
extern int godot_audio_sample_is_active(const char *p_playback_object_id);
extern void godot_audio_sample_update_pitch_scale(const char *p_playback_object_id, float p_pitch_scale);
extern void godot_audio_sample_set_volumes_linear(const char *p_playback_object_id, int *p_buses_buf, int p_buses_size, float *p_volumes_buf, int p_volumes_size);
extern void godot_audio_sample_bus_set_count(int p_count);
extern void godot_audio_sample_bus_remove(int p_index);
extern void godot_audio_sample_bus_add(int p_at_pos = -1);
extern void godot_audio_sample_bus_move(int p_bus, int p_to_pos);
extern void godot_audio_sample_bus_set_send(int p_bus, int p_send_index);
extern void godot_audio_sample_bus_set_volume_db(int p_bus, float p_volume_db);
extern void godot_audio_sample_bus_set_solo(int p_bus, bool p_enable);
extern void godot_audio_sample_bus_set_mute(int p_bus, bool p_enable);
// Worklet
typedef int32_t GodotAudioState[4];
extern int godot_audio_worklet_create(int p_channels);

View File

@ -76,7 +76,8 @@ const Features = {
*/
getMissingFeatures: function (supportedFeatures = {}) {
const {
threads: supportsThreads = true,
// Quotes are needed for the Closure compiler.
'threads': supportsThreads = true,
} = supportedFeatures;
const missing = [];

File diff suppressed because it is too large Load Diff

View File

@ -128,6 +128,8 @@ void AudioStreamPlayer2D::_update_panning() {
volume_vector.write[2] = AudioFrame(0, 0);
volume_vector.write[3] = AudioFrame(0, 0);
StringName actual_bus = _get_actual_bus();
for (Viewport *vp : viewports) {
if (!vp->is_audio_listener_2d()) {
continue;
@ -172,15 +174,20 @@ void AudioStreamPlayer2D::_update_panning() {
const AudioFrame &prev_sample = volume_vector[0];
AudioFrame new_sample = AudioFrame(l, r) * multiplier;
volume_vector.write[0] = AudioFrame(MAX(prev_sample[0], new_sample[0]), MAX(prev_sample[1], new_sample[1]));
}
for (const Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) {
AudioServer::get_singleton()->set_playback_bus_exclusive(playback, _get_actual_bus(), volume_vector);
AudioServer::get_singleton()->set_playback_bus_exclusive(playback, actual_bus, volume_vector);
}
for (Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) {
for (const Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) {
AudioServer::get_singleton()->set_playback_pitch_scale(playback, internal->pitch_scale);
if (playback->get_is_sample() && playback->get_sample_playback().is_valid()) {
Ref<AudioSamplePlayback> sample_playback = playback->get_sample_playback();
AudioServer::get_singleton()->update_sample_playback_pitch_scale(sample_playback, internal->pitch_scale);
}
}
last_mix_count = AudioServer::get_singleton()->get_mix_count();
@ -218,6 +225,15 @@ void AudioStreamPlayer2D::play(float p_from_pos) {
}
setplayback = stream_playback;
setplay.set(p_from_pos);
// Sample handling.
if (stream_playback->get_is_sample() && stream_playback->get_sample_playback().is_valid()) {
Ref<AudioSamplePlayback> sample_playback = stream_playback->get_sample_playback();
sample_playback->offset = p_from_pos;
sample_playback->bus = _get_actual_bus();
AudioServer::get_singleton()->start_sample_playback(sample_playback);
}
}
void AudioStreamPlayer2D::seek(float p_seconds) {
@ -326,6 +342,14 @@ float AudioStreamPlayer2D::get_panning_strength() const {
return panning_strength;
}
AudioServer::PlaybackType AudioStreamPlayer2D::get_playback_type() const {
return internal->get_playback_type();
}
void AudioStreamPlayer2D::set_playback_type(AudioServer::PlaybackType p_playback_type) {
internal->set_playback_type(p_playback_type);
}
bool AudioStreamPlayer2D::_set(const StringName &p_name, const Variant &p_value) {
return internal->set(p_name, p_value);
}
@ -385,6 +409,9 @@ void AudioStreamPlayer2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer2D::has_stream_playback);
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer2D::get_stream_playback);
ClassDB::bind_method(D_METHOD("set_playback_type", "playback_type"), &AudioStreamPlayer2D::set_playback_type);
ClassDB::bind_method(D_METHOD("get_playback_type"), &AudioStreamPlayer2D::get_playback_type);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale");
@ -397,6 +424,7 @@ void AudioStreamPlayer2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "panning_strength", PROPERTY_HINT_RANGE, "0,3,0.01,or_greater"), "set_panning_strength", "get_panning_strength");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus");
ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask");
ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_type", PROPERTY_HINT_ENUM, "Default,Stream,Sample"), "set_playback_type", "get_playback_type");
ADD_SIGNAL(MethodInfo("finished"));
}

View File

@ -32,6 +32,7 @@
#define AUDIO_STREAM_PLAYER_2D_H
#include "scene/2d/node_2d.h"
#include "servers/audio_server.h"
struct AudioFrame;
class AudioStream;
@ -64,6 +65,8 @@ private:
uint64_t last_mix_count = -1;
bool force_update_panning = false;
AudioServer::PlaybackType playback_type = AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT;
void _set_playing(bool p_enable);
bool _is_active() const;
@ -137,6 +140,9 @@ public:
bool has_stream_playback();
Ref<AudioStreamPlayback> get_stream_playback();
AudioServer::PlaybackType get_playback_type() const;
void set_playback_type(AudioServer::PlaybackType p_playback_type);
AudioStreamPlayer2D();
~AudioStreamPlayer2D();
};

View File

@ -482,6 +482,12 @@ Vector<AudioFrame> AudioStreamPlayer3D::_update_panning() {
}
for (Ref<AudioStreamPlayback> &playback : internal->stream_playbacks) {
AudioServer::get_singleton()->set_playback_pitch_scale(playback, actual_pitch_scale);
if (playback->get_is_sample()) {
Ref<AudioSamplePlayback> sample_playback = playback->get_sample_playback();
if (sample_playback.is_valid()) {
AudioServer::get_singleton()->update_sample_playback_pitch_scale(sample_playback, actual_pitch_scale);
}
}
}
}
return output_volume_vector;
@ -536,6 +542,15 @@ void AudioStreamPlayer3D::play(float p_from_pos) {
}
setplayback = stream_playback;
setplay.set(p_from_pos);
// Sample handling.
if (stream_playback->get_is_sample()) {
Ref<AudioSamplePlayback> sample_playback = stream_playback->get_sample_playback();
sample_playback->offset = p_from_pos;
sample_playback->bus = _get_actual_bus();
AudioServer::get_singleton()->start_sample_playback(sample_playback);
}
}
void AudioStreamPlayer3D::seek(float p_seconds) {
@ -715,6 +730,14 @@ float AudioStreamPlayer3D::get_panning_strength() const {
return panning_strength;
}
AudioServer::PlaybackType AudioStreamPlayer3D::get_playback_type() const {
return internal->get_playback_type();
}
void AudioStreamPlayer3D::set_playback_type(AudioServer::PlaybackType p_playback_type) {
internal->set_playback_type(p_playback_type);
}
bool AudioStreamPlayer3D::_set(const StringName &p_name, const Variant &p_value) {
return internal->set(p_name, p_value);
}
@ -798,6 +821,9 @@ void AudioStreamPlayer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer3D::has_stream_playback);
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer3D::get_stream_playback);
ClassDB::bind_method(D_METHOD("set_playback_type", "playback_type"), &AudioStreamPlayer3D::set_playback_type);
ClassDB::bind_method(D_METHOD("get_playback_type"), &AudioStreamPlayer3D::get_playback_type);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
ADD_PROPERTY(PropertyInfo(Variant::INT, "attenuation_model", PROPERTY_HINT_ENUM, "Inverse,Inverse Square,Logarithmic,Disabled"), "set_attenuation_model", "get_attenuation_model");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,80,suffix:dB"), "set_volume_db", "get_volume_db");
@ -812,6 +838,7 @@ void AudioStreamPlayer3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "panning_strength", PROPERTY_HINT_RANGE, "0,3,0.01,or_greater"), "set_panning_strength", "get_panning_strength");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus");
ADD_PROPERTY(PropertyInfo(Variant::INT, "area_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_area_mask", "get_area_mask");
ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_type", PROPERTY_HINT_ENUM, "Default,Stream,Sample"), "set_playback_type", "get_playback_type");
ADD_GROUP("Emission Angle", "emission_angle");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emission_angle_enabled"), "set_emission_angle_enabled", "is_emission_angle_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_angle_degrees", PROPERTY_HINT_RANGE, "0.1,90,0.1,degrees"), "set_emission_angle", "get_emission_angle");

View File

@ -32,6 +32,7 @@
#define AUDIO_STREAM_PLAYER_3D_H
#include "scene/3d/node_3d.h"
#include "servers/audio_server.h"
class Area3D;
struct AudioFrame;
@ -93,6 +94,8 @@ private:
uint32_t area_mask = 1;
AudioServer::PlaybackType playback_type = AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT;
bool emission_angle_enabled = false;
float emission_angle = 45.0;
float emission_angle_filter_attenuation_db = -12.0;
@ -193,6 +196,9 @@ public:
bool has_stream_playback();
Ref<AudioStreamPlayback> get_stream_playback();
AudioServer::PlaybackType get_playback_type() const;
void set_playback_type(AudioServer::PlaybackType p_playback_type);
AudioStreamPlayer3D();
~AudioStreamPlayer3D();
};

View File

@ -33,11 +33,15 @@
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "scene/2d/audio_stream_player_2d.h"
#include "scene/animation/animation_player.h"
#include "scene/audio/audio_stream_player.h"
#include "scene/resources/animation.h"
#include "servers/audio/audio_stream.h"
#include "servers/audio_server.h"
#ifndef _3D_DISABLED
#include "scene/3d/audio_stream_player_3d.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/node_3d.h"
#include "scene/3d/skeleton_3d.h"
@ -602,8 +606,8 @@ bool AnimationMixer::_update_caches() {
root_motion_cache.rot = Quaternion(0, 0, 0, 1);
root_motion_cache.scale = Vector3(1, 1, 1);
List<StringName> sname;
get_animation_list(&sname);
List<StringName> sname_list;
get_animation_list(&sname_list);
bool check_path = GLOBAL_GET("animation/warnings/check_invalid_track_paths");
bool check_angle_interpolation = GLOBAL_GET("animation/warnings/check_angle_interpolation_type_conflicting");
@ -632,7 +636,7 @@ bool AnimationMixer::_update_caches() {
if (has_reset_anim) {
reset_anim = get_animation(SceneStringName(RESET));
}
for (const StringName &E : sname) {
for (const StringName &E : sname_list) {
Ref<Animation> anim = get_animation(E);
for (int i = 0; i < anim->get_track_count(); i++) {
NodePath path = anim->track_get_path(i);
@ -833,6 +837,8 @@ bool AnimationMixer::_update_caches() {
track_audio->object_id = child->get_instance_id();
track_audio->audio_stream.instantiate();
track_audio->audio_stream->set_polyphony(audio_max_polyphony);
track_audio->playback_type = (AudioServer::PlaybackType)(int)(child->call(SNAME("get_playback_type")));
track_audio->bus = (StringName)(child->call(SNAME("get_bus")));
track = track_audio;
@ -1585,6 +1591,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
if (idx < 0) {
continue;
}
// Play stream.
Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
if (stream.is_valid()) {
@ -1594,6 +1601,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
if (seeked) {
start_ofs += time - a->track_get_key_time(i, idx);
}
if (t_obj->call(SNAME("get_stream")) != t->audio_stream) {
t_obj->call(SNAME("set_stream"), t->audio_stream);
t->audio_stream_playback.unref();
@ -1611,8 +1619,18 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
if (t->audio_stream_playback.is_null()) {
t->audio_stream_playback = t_obj->call(SNAME("get_stream_playback"));
}
if (t_obj->call(SNAME("get_is_sample"))) {
Ref<AudioSamplePlayback> sample_playback;
sample_playback.instantiate();
sample_playback->stream = stream;
t->audio_stream_playback->set_sample_playback(sample_playback);
AudioServer::get_singleton()->start_sample_playback(sample_playback);
continue;
}
PlayingAudioStreamInfo pasi;
pasi.index = t->audio_stream_playback->play_stream(stream, start_ofs);
pasi.index = t->audio_stream_playback->play_stream(stream, start_ofs, 0, 1.0, t->playback_type, t->bus);
pasi.start = time;
if (len && end_ofs > 0) { // Force an end at a time.
pasi.len = len - start_ofs - end_ofs;
@ -2255,6 +2273,7 @@ void AnimationMixer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_callback_mode_discrete", "mode"), &AnimationMixer::set_callback_mode_discrete);
ClassDB::bind_method(D_METHOD("get_callback_mode_discrete"), &AnimationMixer::get_callback_mode_discrete);
/* ---- Audio ---- */
ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationMixer::set_audio_max_polyphony);
ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationMixer::get_audio_max_polyphony);

View File

@ -279,12 +279,15 @@ protected:
Ref<AudioStreamPolyphonic> audio_stream;
Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
HashMap<ObjectID, PlayingAudioTrackInfo> playing_streams; // Key is Animation resource ObjectID.
AudioServer::PlaybackType playback_type;
StringName bus;
TrackCacheAudio(const TrackCacheAudio &p_other) :
TrackCache(p_other),
audio_stream(p_other.audio_stream),
audio_stream_playback(p_other.audio_stream_playback),
playing_streams(p_other.playing_streams) {}
playing_streams(p_other.playing_streams),
playback_type(p_other.playback_type) {}
TrackCacheAudio() {
type = Animation::TYPE_AUDIO;
@ -313,6 +316,9 @@ protected:
void _init_root_motion_cache();
bool _update_caches();
/* ---- Audio ---- */
AudioServer::PlaybackType playback_type;
/* ---- Blending processor ---- */
LocalVector<AnimationInstance> animation_instances;
HashMap<NodePath, int> track_map;
@ -425,6 +431,7 @@ public:
void set_callback_mode_discrete(AnimationCallbackModeDiscrete p_mode);
AnimationCallbackModeDiscrete get_callback_mode_discrete() const;
/* ---- Audio ---- */
void set_audio_max_polyphony(int p_audio_max_polyphony);
int get_audio_max_polyphony() const;

View File

@ -95,6 +95,16 @@ void AudioStreamPlayer::play(float p_from_pos) {
}
AudioServer::get_singleton()->start_playback_stream(stream_playback, internal->bus, _get_volume_vector(), p_from_pos, internal->pitch_scale);
internal->ensure_playback_limit();
// Sample handling.
if (stream_playback->get_is_sample() && stream_playback->get_sample_playback().is_valid()) {
Ref<AudioSamplePlayback> sample_playback = stream_playback->get_sample_playback();
sample_playback->offset = p_from_pos;
sample_playback->volume_vector = _get_volume_vector();
sample_playback->bus = get_bus();
AudioServer::get_singleton()->start_sample_playback(sample_playback);
}
}
void AudioStreamPlayer::seek(float p_seconds) {
@ -205,6 +215,14 @@ Ref<AudioStreamPlayback> AudioStreamPlayer::get_stream_playback() {
return internal->get_stream_playback();
}
AudioServer::PlaybackType AudioStreamPlayer::get_playback_type() const {
return internal->get_playback_type();
}
void AudioStreamPlayer::set_playback_type(AudioServer::PlaybackType p_playback_type) {
internal->set_playback_type(p_playback_type);
}
void AudioStreamPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_stream", "stream"), &AudioStreamPlayer::set_stream);
ClassDB::bind_method(D_METHOD("get_stream"), &AudioStreamPlayer::get_stream);
@ -243,6 +261,9 @@ void AudioStreamPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer::has_stream_playback);
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer::get_stream_playback);
ClassDB::bind_method(D_METHOD("set_playback_type", "playback_type"), &AudioStreamPlayer::set_playback_type);
ClassDB::bind_method(D_METHOD("get_playback_type"), &AudioStreamPlayer::get_playback_type);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale");
@ -252,6 +273,7 @@ void AudioStreamPlayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_target", PROPERTY_HINT_ENUM, "Stereo,Surround,Center"), "set_mix_target", "get_mix_target");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_polyphony", PROPERTY_HINT_NONE, ""), "set_max_polyphony", "get_max_polyphony");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus");
ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_type", PROPERTY_HINT_ENUM, "Default,Stream,Sample"), "set_playback_type", "get_playback_type");
ADD_SIGNAL(MethodInfo("finished"));

View File

@ -32,6 +32,7 @@
#define AUDIO_STREAM_PLAYER_H
#include "scene/main/node.h"
#include "servers/audio_server.h"
struct AudioFrame;
class AudioStream;
@ -106,6 +107,9 @@ public:
bool has_stream_playback();
Ref<AudioStreamPlayback> get_stream_playback();
AudioServer::PlaybackType get_playback_type() const;
void set_playback_type(AudioServer::PlaybackType p_playback_type);
AudioStreamPlayer();
~AudioStreamPlayer();
};

View File

@ -141,6 +141,24 @@ Ref<AudioStreamPlayback> AudioStreamPlayerInternal::play_basic() {
stream_playback->set_parameter(K.value.path, K.value.value);
}
// Sample handling.
if (_is_sample()) {
if (stream->can_be_sampled()) {
stream_playback->set_is_sample(true);
if (stream_playback->get_is_sample() && stream_playback->get_sample_playback().is_null()) {
if (!AudioServer::get_singleton()->is_stream_registered_as_sample(stream)) {
AudioServer::get_singleton()->register_stream_as_sample(stream);
}
Ref<AudioSamplePlayback> sample_playback;
sample_playback.instantiate();
sample_playback->stream = stream;
stream_playback->set_sample_playback(sample_playback);
}
} else if (!stream->is_meta_stream()) {
WARN_PRINT(vformat(R"(%s is trying to play a sample from a stream that cannot be sampled.)", node->get_path()));
}
}
stream_playbacks.push_back(stream_playback);
active.set();
_set_process(true);
@ -151,6 +169,9 @@ void AudioStreamPlayerInternal::set_stream_paused(bool p_pause) {
// TODO this does not have perfect recall, fix that maybe? If there are zero playbacks registered with the AudioServer, this bool isn't persisted.
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
AudioServer::get_singleton()->set_playback_paused(playback, p_pause);
if (_is_sample() && playback->get_sample_playback().is_valid()) {
AudioServer::get_singleton()->set_sample_playback_pause(playback->get_sample_playback(), p_pause);
}
}
}
@ -240,8 +261,12 @@ void AudioStreamPlayerInternal::seek(float p_seconds) {
void AudioStreamPlayerInternal::stop() {
for (Ref<AudioStreamPlayback> &playback : stream_playbacks) {
AudioServer::get_singleton()->stop_playback_stream(playback);
if (_is_sample() && playback->get_sample_playback().is_valid()) {
AudioServer::get_singleton()->stop_sample_playback(playback->get_sample_playback());
}
}
stream_playbacks.clear();
active.clear();
_set_process(false);
}
@ -251,6 +276,9 @@ bool AudioStreamPlayerInternal::is_playing() const {
if (AudioServer::get_singleton()->is_playback_active(playback)) {
return true;
}
if (AudioServer::get_singleton()->is_sample_playback_active(playback)) {
return true;
}
}
return false;
}
@ -299,6 +327,14 @@ Ref<AudioStreamPlayback> AudioStreamPlayerInternal::get_stream_playback() {
return stream_playbacks[stream_playbacks.size() - 1];
}
void AudioStreamPlayerInternal::set_playback_type(AudioServer::PlaybackType p_playback_type) {
playback_type = p_playback_type;
}
AudioServer::PlaybackType AudioStreamPlayerInternal::get_playback_type() const {
return playback_type;
}
StringName AudioStreamPlayerInternal::get_bus() const {
const String bus_name = bus;
for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) {

View File

@ -33,14 +33,17 @@
#include "core/object/ref_counted.h"
#include "core/templates/safe_refcount.h"
#include "servers/audio_server.h"
class AudioStream;
class AudioStreamPlayback;
class AudioSamplePlayback;
class Node;
class AudioStreamPlayerInternal : public Object {
GDCLASS(AudioStreamPlayerInternal, Object);
private:
struct ParameterData {
StringName path;
Variant value;
@ -51,12 +54,17 @@ class AudioStreamPlayerInternal : public Object {
Node *node = nullptr;
Callable play_callable;
bool physical = false;
AudioServer::PlaybackType playback_type = AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT;
HashMap<StringName, ParameterData> playback_parameters;
void _set_process(bool p_enabled);
void _update_stream_parameters();
_FORCE_INLINE_ bool _is_sample() {
return (AudioServer::get_singleton()->get_default_playback_type() == AudioServer::PlaybackType::PLAYBACK_TYPE_SAMPLE && get_playback_type() == AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT) || get_playback_type() == AudioServer::PlaybackType::PLAYBACK_TYPE_SAMPLE;
}
public:
Vector<Ref<AudioStreamPlayback>> stream_playbacks;
Ref<AudioStream> stream;
@ -99,6 +107,9 @@ public:
bool has_stream_playback();
Ref<AudioStreamPlayback> get_stream_playback();
void set_playback_type(AudioServer::PlaybackType p_playback_type);
AudioServer::PlaybackType get_playback_type() const;
AudioStreamPlayerInternal(Node *p_node, const Callable &p_play_callable, bool p_physical);
};

View File

@ -0,0 +1,41 @@
/**************************************************************************/
/* audio_stream_polyphonic.compat.inc */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
AudioStreamPlaybackPolyphonic::ID AudioStreamPlaybackPolyphonic::_play_stream_bind_compat_91382(const Ref<AudioStream> &p_stream, float p_from_offset, float p_volume_db, float p_pitch_scale) {
return play_stream(p_stream, p_from_offset, p_volume_db, p_pitch_scale, AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT, SceneStringName(Master));
}
void AudioStreamPlaybackPolyphonic::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("play_stream", "stream", "from_offset", "volume_db", "pitch_scale"), &AudioStreamPlaybackPolyphonic::_play_stream_bind_compat_91382, DEFVAL(0), DEFVAL(0), DEFVAL(1.0));
}
#endif

View File

@ -29,7 +29,10 @@
/**************************************************************************/
#include "audio_stream_polyphonic.h"
#include "audio_stream_polyphonic.compat.inc"
#include "scene/main/scene_tree.h"
#include "servers/audio_server.h"
Ref<AudioStreamPlayback> AudioStreamPolyphonic::instantiate_playback() {
Ref<AudioStreamPlaybackPolyphonic> playback;
@ -136,6 +139,10 @@ int AudioStreamPlaybackPolyphonic::mix(AudioFrame *p_buffer, float p_rate_scale,
continue;
}
if (s.stream_playback->get_is_sample()) {
continue;
}
float volume_db = s.volume_db; // Copy because it can be overridden at any time.
float next_volume = Math::db_to_linear(volume_db);
s.prev_volume_db = volume_db;
@ -195,8 +202,13 @@ int AudioStreamPlaybackPolyphonic::mix(AudioFrame *p_buffer, float p_rate_scale,
return p_frames;
}
AudioStreamPlaybackPolyphonic::ID AudioStreamPlaybackPolyphonic::play_stream(const Ref<AudioStream> &p_stream, float p_from_offset, float p_volume_db, float p_pitch_scale) {
AudioStreamPlaybackPolyphonic::ID AudioStreamPlaybackPolyphonic::play_stream(const Ref<AudioStream> &p_stream, float p_from_offset, float p_volume_db, float p_pitch_scale, AudioServer::PlaybackType p_playback_type, const StringName &p_bus) {
ERR_FAIL_COND_V(p_stream.is_null(), INVALID_ID);
AudioServer::PlaybackType playback_type = p_playback_type == AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT
? AudioServer::get_singleton()->get_default_playback_type()
: p_playback_type;
for (uint32_t i = 0; i < streams.size(); i++) {
if (!streams[i].active.is_set()) {
// Can use this stream, as it's not active.
@ -210,6 +222,28 @@ AudioStreamPlaybackPolyphonic::ID AudioStreamPlaybackPolyphonic::play_stream(con
streams[i].finish_request.clear();
streams[i].pending_play.set();
streams[i].active.set();
// Sample playback.
if (playback_type == AudioServer::PlaybackType::PLAYBACK_TYPE_SAMPLE && p_stream->can_be_sampled()) {
streams[i].stream_playback->set_is_sample(true);
if (!AudioServer::get_singleton()->is_stream_registered_as_sample(p_stream)) {
AudioServer::get_singleton()->register_stream_as_sample(p_stream);
}
float linear_volume = Math::db_to_linear(p_volume_db);
Ref<AudioSamplePlayback> sp;
sp.instantiate();
sp->stream = streams[i].stream;
sp->offset = p_from_offset;
sp->volume_vector.resize(4);
sp->volume_vector.write[0] = AudioFrame(linear_volume, linear_volume);
sp->volume_vector.write[1] = AudioFrame(linear_volume, /* LFE= */ 1.0f);
sp->volume_vector.write[2] = AudioFrame(linear_volume, linear_volume);
sp->volume_vector.write[3] = AudioFrame(linear_volume, linear_volume);
sp->bus = p_bus;
streams[i].stream_playback->set_sample_playback(sp);
AudioServer::get_singleton()->start_sample_playback(sp);
}
return (ID(i) << INDEX_SHIFT) | ID(streams[i].id);
}
}
@ -260,8 +294,24 @@ void AudioStreamPlaybackPolyphonic::stop_stream(ID p_stream_id) {
s->finish_request.set();
}
void AudioStreamPlaybackPolyphonic::set_is_sample(bool p_is_sample) {
_is_sample = p_is_sample;
}
bool AudioStreamPlaybackPolyphonic::get_is_sample() const {
return _is_sample;
}
Ref<AudioSamplePlayback> AudioStreamPlaybackPolyphonic::get_sample_playback() const {
return sample_playback;
}
void AudioStreamPlaybackPolyphonic::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
sample_playback = p_playback;
}
void AudioStreamPlaybackPolyphonic::_bind_methods() {
ClassDB::bind_method(D_METHOD("play_stream", "stream", "from_offset", "volume_db", "pitch_scale"), &AudioStreamPlaybackPolyphonic::play_stream, DEFVAL(0), DEFVAL(0), DEFVAL(1.0));
ClassDB::bind_method(D_METHOD("play_stream", "stream", "from_offset", "volume_db", "pitch_scale", "playback_type", "bus"), &AudioStreamPlaybackPolyphonic::play_stream, DEFVAL(0), DEFVAL(0), DEFVAL(1.0), DEFVAL(0), DEFVAL(SceneStringName(Master)));
ClassDB::bind_method(D_METHOD("set_stream_volume", "stream", "volume_db"), &AudioStreamPlaybackPolyphonic::set_stream_volume);
ClassDB::bind_method(D_METHOD("set_stream_pitch_scale", "stream", "pitch_scale"), &AudioStreamPlaybackPolyphonic::set_stream_pitch_scale);
ClassDB::bind_method(D_METHOD("is_stream_playing", "stream"), &AudioStreamPlaybackPolyphonic::is_stream_playing);

View File

@ -32,12 +32,16 @@
#define AUDIO_STREAM_POLYPHONIC_H
#include "core/templates/local_vector.h"
#include "scene/scene_string_names.h"
#include "servers/audio/audio_stream.h"
#include "servers/audio_server.h"
class AudioStreamPolyphonic : public AudioStream {
GDCLASS(AudioStreamPolyphonic, AudioStream)
int polyphony = 32;
AudioServer::PlaybackType playback_type;
static void _bind_methods();
public:
@ -48,6 +52,8 @@ public:
void set_polyphony(int p_voices);
int get_polyphony() const;
virtual bool is_meta_stream() const override { return true; }
AudioStreamPolyphonic();
};
@ -81,6 +87,9 @@ class AudioStreamPlaybackPolyphonic : public AudioStreamPlayback {
bool active = false;
uint32_t id_counter = 1;
bool _is_sample = false;
Ref<AudioSamplePlayback> sample_playback;
_FORCE_INLINE_ Stream *_find_stream(int64_t p_id);
friend class AudioStreamPolyphonic;
@ -107,12 +116,24 @@ public:
virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) override;
ID play_stream(const Ref<AudioStream> &p_stream, float p_from_offset = 0, float p_volume_db = 0, float p_pitch_scale = 1.0);
ID play_stream(const Ref<AudioStream> &p_stream, float p_from_offset = 0, float p_volume_db = 0, float p_pitch_scale = 1.0, AudioServer::PlaybackType p_playback_type = AudioServer::PlaybackType::PLAYBACK_TYPE_DEFAULT, const StringName &p_bus = SceneStringName(Master));
void set_stream_volume(ID p_stream_id, float p_volume_db);
void set_stream_pitch_scale(ID p_stream_id, float p_pitch_scale);
bool is_stream_playing(ID p_stream_id) const;
void stop_stream(ID p_stream_id);
virtual void set_is_sample(bool p_is_sample) override;
virtual bool get_is_sample() const override;
virtual Ref<AudioSamplePlayback> get_sample_playback() const override;
virtual void set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) override;
private:
#ifndef DISABLE_DEPRECATED
ID _play_stream_bind_compat_91382(const Ref<AudioStream> &p_stream, float p_from_offset = 0, float p_volume_db = 0, float p_pitch_scale = 1.0);
static void _bind_compatibility_methods();
#endif // DISABLE_DEPRECATED
public:
AudioStreamPlaybackPolyphonic();
};

View File

@ -465,6 +465,22 @@ void AudioStreamPlaybackWAV::tag_used_streams() {
base->tag_used(get_playback_position());
}
void AudioStreamPlaybackWAV::set_is_sample(bool p_is_sample) {
_is_sample = p_is_sample;
}
bool AudioStreamPlaybackWAV::get_is_sample() const {
return _is_sample;
}
Ref<AudioSamplePlayback> AudioStreamPlaybackWAV::get_sample_playback() const {
return sample_playback;
}
void AudioStreamPlaybackWAV::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
sample_playback = p_playback;
}
AudioStreamPlaybackWAV::AudioStreamPlaybackWAV() {}
AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() {
@ -696,6 +712,33 @@ String AudioStreamWAV::get_stream_name() const {
return "";
}
Ref<AudioSample> AudioStreamWAV::generate_sample() const {
Ref<AudioSample> sample;
sample.instantiate();
sample->stream = this;
switch (loop_mode) {
case AudioStreamWAV::LoopMode::LOOP_DISABLED: {
sample->loop_mode = AudioSample::LoopMode::LOOP_DISABLED;
} break;
case AudioStreamWAV::LoopMode::LOOP_FORWARD: {
sample->loop_mode = AudioSample::LoopMode::LOOP_FORWARD;
} break;
case AudioStreamWAV::LoopMode::LOOP_PINGPONG: {
sample->loop_mode = AudioSample::LoopMode::LOOP_PINGPONG;
} break;
case AudioStreamWAV::LoopMode::LOOP_BACKWARD: {
sample->loop_mode = AudioSample::LoopMode::LOOP_BACKWARD;
} break;
}
sample->loop_begin = loop_begin;
sample->loop_end = loop_end;
sample->sample_rate = mix_rate;
return sample;
}
void AudioStreamWAV::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_data", "data"), &AudioStreamWAV::set_data);
ClassDB::bind_method(D_METHOD("get_data"), &AudioStreamWAV::get_data);

View File

@ -78,6 +78,9 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback {
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);
bool _is_sample = false;
Ref<AudioSamplePlayback> sample_playback;
public:
virtual void start(double p_from_pos = 0.0) override;
virtual void stop() override;
@ -92,6 +95,11 @@ public:
virtual void tag_used_streams() override;
virtual void set_is_sample(bool p_is_sample) override;
virtual bool get_is_sample() const override;
virtual Ref<AudioSamplePlayback> get_sample_playback() const override;
virtual void set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) override;
AudioStreamPlaybackWAV();
~AudioStreamPlaybackWAV();
};
@ -166,6 +174,11 @@ public:
virtual Ref<AudioStreamPlayback> instantiate_playback() override;
virtual String get_stream_name() const override;
virtual bool can_be_sampled() const override {
return true;
}
virtual Ref<AudioSample> generate_sample() const override;
AudioStreamWAV();
~AudioStreamWAV();
};

View File

@ -90,6 +90,10 @@ Variant AudioStreamPlayback::get_parameter(const StringName &p_name) const {
return ret;
}
Ref<AudioSamplePlayback> AudioStreamPlayback::get_sample_playback() const {
return nullptr;
}
void AudioStreamPlayback::_bind_methods() {
GDVIRTUAL_BIND(_start, "from_pos")
GDVIRTUAL_BIND(_stop)
@ -101,6 +105,17 @@ void AudioStreamPlayback::_bind_methods() {
GDVIRTUAL_BIND(_tag_used_streams);
GDVIRTUAL_BIND(_set_parameter, "name", "value");
GDVIRTUAL_BIND(_get_parameter, "name");
ClassDB::bind_method(D_METHOD("set_sample_playback", "playback_sample"), &AudioStreamPlayback::set_sample_playback);
ClassDB::bind_method(D_METHOD("get_sample_playback"), &AudioStreamPlayback::get_sample_playback);
}
AudioStreamPlayback::AudioStreamPlayback() {}
AudioStreamPlayback::~AudioStreamPlayback() {
if (get_sample_playback().is_valid() && likely(AudioServer::get_singleton() != nullptr)) {
AudioServer::get_singleton()->stop_sample_playback(get_sample_playback());
}
}
//////////////////////////////
@ -271,10 +286,22 @@ void AudioStream::get_parameter_list(List<Parameter> *r_parameters) {
}
}
Ref<AudioSample> AudioStream::generate_sample() const {
ERR_FAIL_COND_V_MSG(!can_be_sampled(), nullptr, "Cannot generate a sample for a stream that cannot be sampled.");
Ref<AudioSample> sample;
sample.instantiate();
sample->stream = this;
return sample;
}
void AudioStream::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_length"), &AudioStream::get_length);
ClassDB::bind_method(D_METHOD("is_monophonic"), &AudioStream::is_monophonic);
ClassDB::bind_method(D_METHOD("instantiate_playback"), &AudioStream::instantiate_playback);
ClassDB::bind_method(D_METHOD("can_be_sampled"), &AudioStream::can_be_sampled);
ClassDB::bind_method(D_METHOD("generate_sample"), &AudioStream::generate_sample);
ClassDB::bind_method(D_METHOD("is_meta_stream"), &AudioStream::is_meta_stream);
GDVIRTUAL_BIND(_instantiate_playback);
GDVIRTUAL_BIND(_get_stream_name);
GDVIRTUAL_BIND(_get_length);

View File

@ -43,6 +43,39 @@
class AudioStream;
class AudioSamplePlayback : public RefCounted {
GDCLASS(AudioSamplePlayback, RefCounted);
public:
Ref<AudioStream> stream;
float offset = 0.0f;
Vector<AudioFrame> volume_vector;
StringName bus;
};
class AudioSample : public RefCounted {
GDCLASS(AudioSample, RefCounted)
public:
enum LoopMode {
LOOP_DISABLED,
LOOP_FORWARD,
LOOP_PINGPONG,
LOOP_BACKWARD,
};
Ref<AudioStream> stream;
Vector<AudioFrame> data;
int num_channels = 1;
int sample_rate = 44100;
LoopMode loop_mode = LOOP_DISABLED;
int loop_begin = 0;
int loop_end = 0;
};
///////////
class AudioStreamPlayback : public RefCounted {
GDCLASS(AudioStreamPlayback, RefCounted);
@ -75,6 +108,14 @@ public:
virtual Variant get_parameter(const StringName &p_name) const;
virtual int mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames);
virtual void set_is_sample(bool p_is_sample) {}
virtual bool get_is_sample() const { return false; }
virtual Ref<AudioSamplePlayback> get_sample_playback() const;
virtual void set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {}
AudioStreamPlayback();
~AudioStreamPlayback();
};
class AudioStreamPlaybackResampled : public AudioStreamPlayback {
@ -161,6 +202,11 @@ public:
};
virtual void get_parameter_list(List<Parameter> *r_parameters);
virtual bool can_be_sampled() const { return false; }
virtual Ref<AudioSample> generate_sample() const;
virtual bool is_meta_stream() const { return false; }
};
// Microphone
@ -292,6 +338,8 @@ public:
virtual double get_length() const override; //if supported, otherwise return 0
virtual bool is_monophonic() const override;
virtual bool is_meta_stream() const override { return true; }
AudioStreamRandomizer();
};

View File

@ -120,16 +120,18 @@ int AudioDriver::_get_configured_mix_rate() {
StringName audio_driver_setting = "audio/driver/mix_rate";
int mix_rate = GLOBAL_GET(audio_driver_setting);
#ifdef WEB_ENABLED
// `0` is an acceptable value (resorts to the browser's default).
return MAX(0, mix_rate);
#else // !WEB_ENABLED
// In the case of invalid mix rate, let's default to a sensible value..
if (mix_rate <= 0) {
#ifndef WEB_ENABLED
WARN_PRINT(vformat("Invalid mix rate of %d, consider reassigning setting \'%s\'. \nDefaulting mix rate to value %d.",
mix_rate, audio_driver_setting, AudioDriverManager::DEFAULT_MIX_RATE));
#endif
mix_rate = AudioDriverManager::DEFAULT_MIX_RATE;
}
return mix_rate;
#endif
}
AudioDriver::SpeakerMode AudioDriver::get_speaker_mode_by_total_channels(int p_channels) const {
@ -181,6 +183,18 @@ PackedStringArray AudioDriver::get_input_device_list() {
return list;
}
void AudioDriver::start_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
if (p_playback.is_valid()) {
if (p_playback->stream.is_valid()) {
WARN_PRINT_ED(vformat(R"(Trying to play stream (%s) as a sample (%s), but the driver doesn't support sample playback.)", p_playback->get_instance_id(), p_playback->stream->get_instance_id()));
} else {
WARN_PRINT_ED(vformat(R"(Trying to play stream (%s) as a null sample, but the driver doesn't support sample playback.)", p_playback->get_instance_id()));
}
} else {
WARN_PRINT_ED("Trying to play a null sample playback from a driver that don't support sample playback.");
}
}
AudioDriverDummy AudioDriverManager::dummy_driver;
AudioDriver *AudioDriverManager::drivers[MAX_DRIVERS] = {
&AudioDriverManager::dummy_driver,
@ -367,6 +381,10 @@ void AudioServer::_mix_step() {
continue;
}
if (playback->stream_playback->get_is_sample()) {
continue;
}
bool fading_out = playback->state.load() == AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION || playback->state.load() == AudioStreamPlaybackListNode::FADE_OUT_TO_PAUSE;
AudioFrame *buf = mix_buffer.ptrw();
@ -770,6 +788,8 @@ void AudioServer::set_bus_count(int p_count) {
unlock();
AudioDriver::get_singleton()->set_sample_bus_count(p_count);
emit_signal(SNAME("bus_layout_changed"));
}
@ -785,6 +805,8 @@ void AudioServer::remove_bus(int p_index) {
buses.remove_at(p_index);
unlock();
AudioDriver::get_singleton()->remove_sample_bus(p_index);
emit_signal(SNAME("bus_layout_changed"));
}
@ -839,6 +861,8 @@ void AudioServer::add_bus(int p_at_pos) {
buses.insert(p_at_pos, bus);
}
AudioDriver::get_singleton()->add_sample_bus(p_at_pos);
emit_signal(SNAME("bus_layout_changed"));
}
@ -863,6 +887,8 @@ void AudioServer::move_bus(int p_bus, int p_to_pos) {
buses.insert(p_to_pos - 1, bus);
}
AudioDriver::get_singleton()->move_sample_bus(p_bus, p_to_pos);
emit_signal(SNAME("bus_layout_changed"));
}
@ -934,6 +960,8 @@ void AudioServer::set_bus_volume_db(int p_bus, float p_volume_db) {
MARK_EDITED
buses[p_bus]->volume_db = p_volume_db;
AudioDriver::get_singleton()->set_sample_bus_volume_db(p_bus, p_volume_db);
}
float AudioServer::get_bus_volume_db(int p_bus) const {
@ -952,6 +980,8 @@ void AudioServer::set_bus_send(int p_bus, const StringName &p_send) {
MARK_EDITED
buses[p_bus]->send = p_send;
AudioDriver::get_singleton()->set_sample_bus_send(p_bus, p_send);
}
StringName AudioServer::get_bus_send(int p_bus) const {
@ -965,6 +995,8 @@ void AudioServer::set_bus_solo(int p_bus, bool p_enable) {
MARK_EDITED
buses[p_bus]->solo = p_enable;
AudioDriver::get_singleton()->set_sample_bus_solo(p_bus, p_enable);
}
bool AudioServer::is_bus_solo(int p_bus) const {
@ -979,6 +1011,8 @@ void AudioServer::set_bus_mute(int p_bus, bool p_enable) {
MARK_EDITED
buses[p_bus]->mute = p_enable;
AudioDriver::get_singleton()->set_sample_bus_mute(p_bus, p_enable);
}
bool AudioServer::is_bus_mute(int p_bus) const {
@ -1214,6 +1248,13 @@ void AudioServer::set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback
void AudioServer::set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, const HashMap<StringName, Vector<AudioFrame>> &p_bus_volumes) {
ERR_FAIL_COND(p_bus_volumes.size() > MAX_BUSES_PER_PLAYBACK);
// Samples.
if (p_playback->get_is_sample() && p_playback->get_sample_playback().is_valid()) {
Ref<AudioSamplePlayback> sample_playback = p_playback->get_sample_playback();
AudioDriver::get_singleton()->set_sample_playback_bus_volumes_linear(sample_playback, p_bus_volumes);
return;
}
AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
if (!playback_node) {
return;
@ -1265,6 +1306,13 @@ void AudioServer::set_playback_all_bus_volumes_linear(Ref<AudioStreamPlayback> p
void AudioServer::set_playback_pitch_scale(Ref<AudioStreamPlayback> p_playback, float p_pitch_scale) {
ERR_FAIL_COND(p_playback.is_null());
// Samples.
if (p_playback->get_is_sample() && p_playback->get_sample_playback().is_valid()) {
Ref<AudioSamplePlayback> sample_playback = p_playback->get_sample_playback();
AudioServer::get_singleton()->update_sample_playback_pitch_scale(sample_playback, p_pitch_scale);
return;
}
AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
if (!playback_node) {
return;
@ -1385,6 +1433,7 @@ void AudioServer::init() {
if (AudioDriver::get_singleton()) {
AudioDriver::get_singleton()->start();
AudioDriver::get_singleton()->set_sample_bus_count(1);
}
#ifdef TOOLS_ENABLED
@ -1597,6 +1646,9 @@ void AudioServer::set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout) {
}
buses.resize(p_bus_layout->buses.size());
bus_map.clear();
AudioDriver::get_singleton()->set_sample_bus_count(buses.size());
for (int i = 0; i < p_bus_layout->buses.size(); i++) {
Bus *bus = memnew(Bus);
if (i == 0) {
@ -1604,6 +1656,7 @@ void AudioServer::set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout) {
} else {
bus->name = p_bus_layout->buses[i].name;
bus->send = p_bus_layout->buses[i].send;
AudioDriver::get_singleton()->set_sample_bus_send(i, bus->send);
}
bus->solo = p_bus_layout->buses[i].solo;
@ -1611,6 +1664,10 @@ void AudioServer::set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout) {
bus->bypass = p_bus_layout->buses[i].bypass;
bus->volume_db = p_bus_layout->buses[i].volume_db;
AudioDriver::get_singleton()->set_sample_bus_solo(i, bus->solo);
AudioDriver::get_singleton()->set_sample_bus_mute(i, bus->mute);
AudioDriver::get_singleton()->set_sample_bus_volume_db(i, bus->volume_db);
for (int j = 0; j < p_bus_layout->buses[i].effects.size(); j++) {
Ref<AudioEffect> fx = p_bus_layout->buses[i].effects[j].effect;
@ -1638,6 +1695,8 @@ void AudioServer::set_bus_layout(const Ref<AudioBusLayout> &p_bus_layout) {
set_edited(false);
#endif
unlock();
// Samples bus sync.
}
Ref<AudioBusLayout> AudioServer::generate_bus_layout() const {
@ -1705,6 +1764,82 @@ void AudioServer::get_argument_options(const StringName &p_function, int p_idx,
}
#endif
AudioServer::PlaybackType AudioServer::get_default_playback_type() const {
int playback_type = GLOBAL_GET("audio/general/default_playback_type");
ERR_FAIL_COND_V_MSG(
playback_type < 0 || playback_type >= PlaybackType::PLAYBACK_TYPE_MAX,
PlaybackType::PLAYBACK_TYPE_STREAM,
vformat(R"(Project settings value (%s) for "audio/general/default_playback_type" is not supported)", playback_type));
switch (playback_type) {
case 1: {
return PlaybackType::PLAYBACK_TYPE_SAMPLE;
} break;
case 0:
default: {
return PlaybackType::PLAYBACK_TYPE_STREAM;
} break;
}
}
bool AudioServer::is_stream_registered_as_sample(const Ref<AudioStream> &p_stream) {
ERR_FAIL_COND_V_MSG(p_stream.is_null(), false, "Parameter p_stream is null.");
return AudioDriver::get_singleton()->is_stream_registered_as_sample(p_stream);
}
void AudioServer::register_stream_as_sample(const Ref<AudioStream> &p_stream) {
ERR_FAIL_COND_MSG(p_stream.is_null(), "Parameter p_stream is null.");
ERR_FAIL_COND_MSG(!(p_stream->can_be_sampled()), "Parameter p_stream cannot be sampled.");
Ref<AudioSample> sample = p_stream->generate_sample();
register_sample(sample);
}
void AudioServer::unregister_stream_as_sample(const Ref<AudioStream> &p_stream) {
ERR_FAIL_COND_MSG(p_stream.is_null(), "Parameter p_stream is null.");
ERR_FAIL_COND_MSG(!(p_stream->can_be_sampled()), "Parameter p_stream cannot be sampled.");
Ref<AudioSample> sample = p_stream->generate_sample();
unregister_sample(sample);
}
void AudioServer::register_sample(const Ref<AudioSample> &p_sample) {
ERR_FAIL_COND_MSG(p_sample.is_null(), "Parameter p_sample is null.");
ERR_FAIL_COND_MSG(p_sample->stream.is_null(), "Parameter p_sample->stream is null.");
ERR_FAIL_COND_MSG(!(p_sample->stream->can_be_sampled()), "Parameter p_stream cannot be sampled.");
AudioDriver::get_singleton()->register_sample(p_sample);
}
void AudioServer::unregister_sample(const Ref<AudioSample> &p_sample) {
ERR_FAIL_COND_MSG(p_sample.is_null(), "Parameter p_sample is null.");
ERR_FAIL_COND_MSG(p_sample->stream.is_null(), "Parameter p_sample->stream is null.");
AudioDriver::get_singleton()->unregister_sample(p_sample);
}
void AudioServer::start_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null.");
AudioDriver::get_singleton()->start_sample_playback(p_playback);
}
void AudioServer::stop_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null.");
AudioDriver::get_singleton()->stop_sample_playback(p_playback);
}
void AudioServer::set_sample_playback_pause(const Ref<AudioSamplePlayback> &p_playback, bool p_paused) {
ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null.");
AudioDriver::get_singleton()->set_sample_playback_pause(p_playback, p_paused);
}
bool AudioServer::is_sample_playback_active(const Ref<AudioSamplePlayback> &p_playback) {
ERR_FAIL_COND_V_MSG(p_playback.is_null(), false, "Parameter p_playback is null.");
return AudioDriver::get_singleton()->is_sample_playback_active(p_playback);
}
void AudioServer::update_sample_playback_pitch_scale(const Ref<AudioSamplePlayback> &p_playback, float p_pitch_scale) {
ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null.");
return AudioDriver::get_singleton()->update_sample_playback_pitch_scale(p_playback, p_pitch_scale);
}
void AudioServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bus_count", "amount"), &AudioServer::set_bus_count);
ClassDB::bind_method(D_METHOD("get_bus_count"), &AudioServer::get_bus_count);
@ -1774,6 +1909,9 @@ void AudioServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_enable_tagging_used_audio_streams", "enable"), &AudioServer::set_enable_tagging_used_audio_streams);
ClassDB::bind_method(D_METHOD("is_stream_registered_as_sample", "stream"), &AudioServer::is_stream_registered_as_sample);
ClassDB::bind_method(D_METHOD("register_stream_as_sample", "stream"), &AudioServer::register_stream_as_sample);
ADD_PROPERTY(PropertyInfo(Variant::INT, "bus_count"), "set_bus_count", "get_bus_count");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "output_device"), "set_output_device", "get_output_device");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "input_device"), "set_input_device", "get_input_device");
@ -1789,6 +1927,11 @@ void AudioServer::_bind_methods() {
BIND_ENUM_CONSTANT(SPEAKER_SURROUND_31);
BIND_ENUM_CONSTANT(SPEAKER_SURROUND_51);
BIND_ENUM_CONSTANT(SPEAKER_SURROUND_71);
BIND_ENUM_CONSTANT(PLAYBACK_TYPE_DEFAULT);
BIND_ENUM_CONSTANT(PLAYBACK_TYPE_STREAM);
BIND_ENUM_CONSTANT(PLAYBACK_TYPE_SAMPLE);
BIND_ENUM_CONSTANT(PLAYBACK_TYPE_MAX);
}
AudioServer::AudioServer() {

View File

@ -42,9 +42,11 @@
#include <atomic>
class AudioDriverDummy;
class AudioSample;
class AudioStream;
class AudioStreamWAV;
class AudioStreamPlayback;
class AudioSamplePlayback;
class AudioDriver {
static AudioDriver *singleton;
@ -129,6 +131,28 @@ public:
void reset_profiling_time() { prof_time.set(0); }
#endif
// Samples handling.
virtual bool is_stream_registered_as_sample(const Ref<AudioStream> &p_stream) const {
return false;
}
virtual void register_sample(const Ref<AudioSample> &p_sample) {}
virtual void unregister_sample(const Ref<AudioSample> &p_sample) {}
virtual void start_sample_playback(const Ref<AudioSamplePlayback> &p_playback);
virtual void stop_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {}
virtual void set_sample_playback_pause(const Ref<AudioSamplePlayback> &p_playback, bool p_paused) {}
virtual bool is_sample_playback_active(const Ref<AudioSamplePlayback> &p_playback) { return false; }
virtual void update_sample_playback_pitch_scale(const Ref<AudioSamplePlayback> &p_playback, float p_pitch_scale = 0.0f) {}
virtual void set_sample_playback_bus_volumes_linear(const Ref<AudioSamplePlayback> &p_playback, const HashMap<StringName, Vector<AudioFrame>> &p_bus_volumes) {}
virtual void set_sample_bus_count(int p_count) {}
virtual void remove_sample_bus(int p_bus) {}
virtual void add_sample_bus(int p_at_pos = -1) {}
virtual void move_sample_bus(int p_bus, int p_to_pos) {}
virtual void set_sample_bus_send(int p_bus, const StringName &p_send) {}
virtual void set_sample_bus_volume_db(int p_bus, float p_volume_db) {}
virtual void set_sample_bus_solo(int p_bus, bool p_enable) {}
virtual void set_sample_bus_mute(int p_bus, bool p_enable) {}
AudioDriver() {}
virtual ~AudioDriver() {}
};
@ -166,6 +190,13 @@ public:
SPEAKER_SURROUND_71,
};
enum PlaybackType {
PLAYBACK_TYPE_DEFAULT,
PLAYBACK_TYPE_STREAM,
PLAYBACK_TYPE_SAMPLE,
PLAYBACK_TYPE_MAX
};
enum {
AUDIO_DATA_INVALID_ID = -1,
MAX_CHANNELS_PER_BUS = 4,
@ -440,11 +471,25 @@ public:
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#endif
PlaybackType get_default_playback_type() const;
bool is_stream_registered_as_sample(const Ref<AudioStream> &p_stream);
void register_stream_as_sample(const Ref<AudioStream> &p_stream);
void unregister_stream_as_sample(const Ref<AudioStream> &p_stream);
void register_sample(const Ref<AudioSample> &p_sample);
void unregister_sample(const Ref<AudioSample> &p_sample);
void start_sample_playback(const Ref<AudioSamplePlayback> &p_playback);
void stop_sample_playback(const Ref<AudioSamplePlayback> &p_playback);
void set_sample_playback_pause(const Ref<AudioSamplePlayback> &p_playback, bool p_paused);
bool is_sample_playback_active(const Ref<AudioSamplePlayback> &p_playback);
void update_sample_playback_pitch_scale(const Ref<AudioSamplePlayback> &p_playback, float p_pitch_scale = 0.0f);
AudioServer();
virtual ~AudioServer();
};
VARIANT_ENUM_CAST(AudioServer::SpeakerMode)
VARIANT_ENUM_CAST(AudioServer::PlaybackType)
class AudioBusLayout : public Resource {
GDCLASS(AudioBusLayout, Resource);

View File

@ -176,6 +176,8 @@ void register_server_types() {
GDREGISTER_VIRTUAL_CLASS(AudioStreamPlaybackResampled);
GDREGISTER_CLASS(AudioStreamMicrophone);
GDREGISTER_CLASS(AudioStreamRandomizer);
GDREGISTER_CLASS(AudioSample);
GDREGISTER_CLASS(AudioSamplePlayback);
GDREGISTER_VIRTUAL_CLASS(AudioEffect);
GDREGISTER_VIRTUAL_CLASS(AudioEffectInstance);
GDREGISTER_CLASS(AudioEffectEQ);