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); };