diff --git a/doc/classes/SignalSmith.xml b/doc/classes/SignalSmith.xml
index 352f02cfe09..61c46d7f47f 100644
--- a/doc/classes/SignalSmith.xml
+++ b/doc/classes/SignalSmith.xml
@@ -9,6 +9,28 @@
+
+
+
+
+
+
+ Loads an MP3 file, applies time-stretching and pitch-shifting using the Signalsmith engine, and returns a streamable AudioStreamWAV.
+ The `tempo` parameter controls playback speed without affecting pitch. A value of `1.0` preserves the original speed.
+ The `pitch` parameter is a transpose factor. A value of `1.0` preserves pitch, `2.0` raises pitch by one octave,
+ and `0.5` lowers pitch by one octave.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/signalsmith/SCsub b/modules/signalsmith/SCsub
index 3de9dedca81..e28965f4204 100644
--- a/modules/signalsmith/SCsub
+++ b/modules/signalsmith/SCsub
@@ -5,7 +5,12 @@ Import("env")
signalsmith_env = env.Clone()
-signalsmith_env.Append(CPPPATH=["#thirdparty"])
+signalsmith_env.Append(
+ CPPPATH=[
+ "#thirdparty",
+ "#thirdparty/minimp3",
+ ]
+)
cxx = env.subst("$CXX")
is_clang = "clang" in cxx
diff --git a/modules/signalsmith/signalsmith_module.cpp b/modules/signalsmith/signalsmith_module.cpp
index 3ead1acbe6a..dfa8b14329b 100644
--- a/modules/signalsmith/signalsmith_module.cpp
+++ b/modules/signalsmith/signalsmith_module.cpp
@@ -32,8 +32,14 @@
#include "signalsmith_module.h"
+#include "core/io/file_access.h"
#include "core/os/memory.h"
+#include "modules/minimp3/audio_stream_mp3.h"
+#include "scene/resources/audio_stream_wav.h"
+#include "servers/audio/audio_stream.h"
+#include "servers/audio_server.h"
+
#include
#include
@@ -42,8 +48,11 @@ void SignalSmith::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_channels", "channels"), &SignalSmith::set_channels);
ClassDB::bind_method(D_METHOD("set_pitch", "pitch"), &SignalSmith::set_pitch);
ClassDB::bind_method(D_METHOD("set_tempo", "tempo"), &SignalSmith::set_tempo);
+ ClassDB::bind_method(D_METHOD("get_last_sample_rate"), &SignalSmith::get_last_sample_rate);
+ ClassDB::bind_method(D_METHOD("get_last_channels"), &SignalSmith::get_last_channels);
ClassDB::bind_method(D_METHOD("reset"), &SignalSmith::reset);
ClassDB::bind_method(D_METHOD("process", "input"), &SignalSmith::process);
+ ClassDB::bind_method(D_METHOD("change_tempo", "path", "tempo", "pitch"), &SignalSmith::change_tempo, DEFVAL(1.0f));
}
SignalSmith::SignalSmith() {
@@ -86,6 +95,14 @@ void SignalSmith::set_tempo(float p_tempo) {
tempo = p_tempo;
}
+int SignalSmith::get_last_sample_rate() const {
+ return sample_rate;
+}
+
+int SignalSmith::get_last_channels() const {
+ return channels;
+}
+
void SignalSmith::reset() {
stretch.reset();
}
@@ -171,3 +188,86 @@ PackedFloat32Array SignalSmith::process(const PackedFloat32Array &input) {
return output;
}
+
+Ref SignalSmith::change_tempo(const String &path, float p_tempo, float p_pitch) {
+ Ref out;
+
+ Ref mp3 = AudioStreamMP3::load_from_file(path);
+ ERR_FAIL_COND_V(mp3.is_null(), out);
+
+ Ref pb_base = mp3->instantiate_playback();
+ ERR_FAIL_COND_V(pb_base.is_null(), out);
+
+ AudioStreamPlaybackResampled *pb = Object::cast_to(pb_base.ptr());
+
+ ERR_FAIL_COND_V(pb == nullptr, out);
+
+ pb->start(0.0);
+
+ const int channels = mp3->is_monophonic() ? 1 : 2;
+ const int sample_rate = AudioServer::get_singleton()->get_mix_rate();
+
+ Vector frames;
+ const int block = 1024;
+
+ while (true) {
+ int old = frames.size();
+ frames.resize(old + block);
+ int mixed = pb->mix(frames.ptrw() + old, 1.0f, block);
+
+ if (mixed <= 0) {
+ frames.resize(old + mixed);
+ break;
+ }
+ }
+
+ ERR_FAIL_COND_V(frames.is_empty(), out);
+
+ // Convert AudioFrame to float PCM
+ PackedFloat32Array input;
+ input.resize(frames.size() * channels);
+ float *input_w = input.ptrw();
+
+ for (int i = 0; i < frames.size(); i++) {
+ if (channels == 2) {
+ input_w[i * 2 + 0] = frames[i].left;
+ input_w[i * 2 + 1] = frames[i].right;
+ } else {
+ input_w[i] = frames[i].left;
+ }
+ }
+
+ set_sample_rate(sample_rate);
+ set_channels(channels);
+ set_tempo(p_tempo);
+ set_pitch(p_pitch);
+
+ reset();
+
+ PackedFloat32Array processed = process(input);
+ ERR_FAIL_COND_V(processed.is_empty(), out);
+
+ // convert float pcm to pcm16
+ PackedByteArray pcm16;
+ pcm16.resize(processed.size() * 2);
+ uint8_t *pcm_w = pcm16.ptrw();
+
+ const float *proc_r = processed.ptr();
+
+ for (int i = 0; i < processed.size(); i++) {
+ float s = CLAMP(proc_r[i], -1.0f, 1.0f);
+ int16_t v = int16_t(s * 32767.0f);
+
+ pcm_w[i * 2 + 0] = uint8_t(v & 0xff);
+ pcm_w[i * 2 + 1] = uint8_t((v >> 8) & 0xff);
+ }
+
+ // Builds a streamable WAV
+ out.instantiate();
+ out->set_mix_rate(sample_rate);
+ out->set_stereo(channels == 2);
+ out->set_format(AudioStreamWAV::FORMAT_16_BITS);
+ out->set_data(pcm16);
+
+ return out;
+}
diff --git a/modules/signalsmith/signalsmith_module.h b/modules/signalsmith/signalsmith_module.h
index e60453dee84..1803c1f455f 100644
--- a/modules/signalsmith/signalsmith_module.h
+++ b/modules/signalsmith/signalsmith_module.h
@@ -34,6 +34,7 @@
#include "core/object/class_db.h"
#include "core/object/ref_counted.h"
+#include "scene/resources/audio_stream_wav.h"
#include "signalsmith-stretch/signalsmith-stretch.h"
#include
@@ -59,5 +60,10 @@ class SignalSmith : public RefCounted {
void set_tempo(float p_tempo);
void reset();
+ int get_last_sample_rate() const;
+ int get_last_channels() const;
+
PackedFloat32Array process(const PackedFloat32Array &input);
+
+ Ref change_tempo(const String &path, float p_tempo, float p_pitch = 1.0f);
};