Repository navigation
Updated the SignalSmith module to be much easier to work with. #1164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 <cmath> | ||
| #include <vector> | ||
|
|
||
|
|
@@ -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<AudioStreamWAV> SignalSmith::change_tempo(const String &path, float p_tempo, float p_pitch) { | ||
| Ref<AudioStreamWAV> out; | ||
|
|
||
| Ref<AudioStreamMP3> mp3 = AudioStreamMP3::load_from_file(path); | ||
| ERR_FAIL_COND_V(mp3.is_null(), out); | ||
|
|
||
| Ref<AudioStreamPlayback> pb_base = mp3->instantiate_playback(); | ||
| ERR_FAIL_COND_V(pb_base.is_null(), out); | ||
|
|
||
| AudioStreamPlaybackResampled *pb = Object::cast_to<AudioStreamPlaybackResampled>(pb_base.ptr()); | ||
|
|
||
| ERR_FAIL_COND_V(pb == nullptr, out); | ||
|
|
||
| pb->start(0.0); | ||
|
Arctis-Fireblight marked this conversation as resolved.
|
||
|
|
||
| const int channels = mp3->is_monophonic() ? 1 : 2; | ||
| const int sample_rate = AudioServer::get_singleton()->get_mix_rate(); | ||
|
|
||
| Vector<AudioFrame> 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; | ||
| } | ||
| } | ||
|
Comment on lines
+213
to
+222
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle partial mixes to avoid trailing silence. If 🔧 Proposed fix 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);
break;
}
+
+ if (mixed < block) {
+ frames.resize(old + mixed);
+ break;
+ }
}🤖 Prompt for AI Agents
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mcdubhghlas can We may want to consider @coderabbitai 's suggested fix here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: Length of output: 2022 🏁 Script executed: Length of output: 4892 🏁 Script executed: Length of output: 12087 🏁 Script executed: Length of output: 4572 🏁 Script executed: Length of output: 14268
Based on the source code analysis, The method internally calls Current issue: When Recommendation: CodeRabbit's suggested fix is correct and should be applied. The loop should handle the partial buffer case: if (mixed < block) {
frames.resize(old + mixed);
break;
}This ensures only the actual mixed frames are preserved, preventing trailing silence or buffer underflow artifacts in the processed audio output. 🧬 Code Graph Analysis Results[modules/signalsmith/signalsmith_module.cpp:change_tempo function] }
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks CodeRabbit! @mcdubhghlas do you mind making the suggested fix and then rebasing off of the current There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| 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; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add short descriptions for the new getters.
Empty descriptions make it unclear what “last” refers to for users.
📝 Suggested doc update
🤖 Prompt for AI Agents