Skip to content

Commit

Permalink
Improve AudioStream looping
Browse files Browse the repository at this point in the history
* AudioStream (OGG/MP3) import dialog now allows to optionally set loop in beats.
* Loop points other than zero now properly fade-in to avoid clicks. Keep in mind if you have strong transients in the loop they may be smoothed due to the fade, so consider eventually separating in two clips and use upcoming AudioStreamPlaybackPlaylist or AudioStreamPlaybackInteractive.
* Stream import dialog now indicates the beat where loop occurs.
* Cleaned up the import dialog a bit.
  • Loading branch information
reduz committed Dec 26, 2023
1 parent 13a0d6e commit 89b20b8
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 8 deletions.
53 changes: 47 additions & 6 deletions editor/import/audio_stream_import_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ void AudioStreamImportSettingsDialog::_draw_preview() {
Size2 rect_size = rect.size;
int width = rect_size.width;

Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream);
Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream_no_loop);
float preview_offset = zoom_bar->get_value();
float preview_len = zoom_bar->get_page();

Expand All @@ -104,6 +104,7 @@ void AudioStreamImportSettingsDialog::_draw_preview() {
float inactive_from = 1e20;
float beat_size = 0;
int last_beat = 0;
int loop_beat = 0;
if (stream->get_bpm() > 0) {
beat_size = 60 / float(stream->get_bpm());
int y_ofs = beat_font->get_height(main_size) + 4 * EDSCALE;
Expand All @@ -114,6 +115,10 @@ void AudioStreamImportSettingsDialog::_draw_preview() {
last_beat = stream->get_beat_count();
inactive_from = last_beat * beat_size;
}

if (loop->is_pressed() && loop_offset_type->get_selected() == 1) {
loop_beat = loop_offset->get_value();
}
}

for (int i = 0; i < width; i++) {
Expand All @@ -136,6 +141,7 @@ void AudioStreamImportSettingsDialog::_draw_preview() {
if (beat_size) {
Color beat_color = Color(1, 1, 1, 1);
Color final_beat_color = beat_color;
Color loop_beat_color = beat_color * Color(1, 0.5, 0.5, 1.0);
Color bar_color = beat_color;
beat_color.a *= 0.4;
bar_color.a *= 0.6;
Expand Down Expand Up @@ -164,6 +170,8 @@ void AudioStreamImportSettingsDialog::_draw_preview() {
// Darken subsequent beats
beat_color.a *= 0.3;
color_active.a *= 0.3;
} else if (beat == loop_beat && loop_beat != 0) {
_preview->draw_rect(Rect2i(i, rect.position.y, 2, rect.size.height), loop_beat_color);
} else {
_preview->draw_rect(Rect2i(i, rect.position.y, 1, rect.size.height), (beat % bar_beats) == 0 ? bar_color : beat_color);
}
Expand All @@ -174,7 +182,7 @@ void AudioStreamImportSettingsDialog::_draw_preview() {
}

void AudioStreamImportSettingsDialog::_preview_changed(ObjectID p_which) {
if (stream.is_valid() && stream->get_instance_id() == p_which) {
if (stream_no_loop.is_valid() && stream_no_loop->get_instance_id() == p_which) {
_preview->queue_redraw();
}
}
Expand Down Expand Up @@ -400,6 +408,14 @@ void AudioStreamImportSettingsDialog::_seek_to(real_t p_x) {
_indicator->queue_redraw();
}

void AudioStreamImportSettingsDialog::_update_loop_offset_type() {
if (loop_offset_type->get_selected() == 0) {
loop_offset->set_step(0.001);
} else {
loop_offset->set_step(1);
}
}

void AudioStreamImportSettingsDialog::edit(const String &p_path, const String &p_importer, const Ref<AudioStream> &p_stream) {
if (!stream.is_null()) {
stream->disconnect_changed(callable_mp(this, &AudioStreamImportSettingsDialog::_audio_changed));
Expand All @@ -410,6 +426,9 @@ void AudioStreamImportSettingsDialog::edit(const String &p_path, const String &p

stream = p_stream;
_player->set_stream(stream);
stream_no_loop = stream->duplicate(true);
stream_no_loop->call("set_loop", false);

_current = 0;
String text = String::num(stream->get_length(), 2).pad_decimals(2) + "s";
_duration_label->set_text(text);
Expand Down Expand Up @@ -437,6 +456,13 @@ void AudioStreamImportSettingsDialog::edit(const String &p_path, const String &p
beats_edit->set_value(beats);
beats_enabled->set_pressed(beats > 0);
loop->set_pressed(config_file->get_value("params", "loop", false));

if (config_file->has_section_key("params", "loop_offset_type")) {
loop_offset_type->select(config_file->get_value("params", "loop_offset_type", 0));
} else {
loop_offset_type->select(0);
}
_update_loop_offset_type();
loop_offset->set_value(config_file->get_value("params", "loop_offset", 0));
bar_beats_edit->set_value(config_file->get_value("params", "bar_beats", 4));

Expand Down Expand Up @@ -471,11 +497,18 @@ void AudioStreamImportSettingsDialog::_settings_changed() {

updating_settings = true;
stream->call("set_loop", loop->is_pressed());
stream->call("set_loop_offset", loop_offset->get_value());
float loop_sec = loop_offset->get_value();
if (loop_offset_type->get_selected() == 1) {
loop_sec *= (60.0 / bpm_edit->get_value());
}

stream->call("set_loop_offset", loop_sec);
if (loop->is_pressed()) {
loop_offset->set_editable(true);
loop_offset_type->set_disabled(false);
} else {
loop_offset->set_editable(false);
loop_offset_type->set_disabled(true);
}

if (bpm_enabled->is_pressed()) {
Expand Down Expand Up @@ -518,6 +551,7 @@ void AudioStreamImportSettingsDialog::_settings_changed() {
void AudioStreamImportSettingsDialog::_reimport() {
params["loop"] = loop->is_pressed();
params["loop_offset"] = loop_offset->get_value();
params["loop_offset_type"] = loop_offset_type->get_selected();
params["bpm"] = bpm_enabled->is_pressed() ? double(bpm_edit->get_value()) : double(0);
params["beat_count"] = (bpm_enabled->is_pressed() && beats_enabled->is_pressed()) ? int(beats_edit->get_value()) : int(0);
params["bar_beats"] = (bpm_enabled->is_pressed()) ? int(bar_beats_edit->get_value()) : int(4);
Expand All @@ -541,13 +575,19 @@ AudioStreamImportSettingsDialog::AudioStreamImportSettingsDialog() {
loop_hb->add_child(loop);
loop_hb->add_spacer();
loop_hb->add_child(memnew(Label(TTR("Offset:"))));

loop_offset = memnew(SpinBox);
loop_offset->set_max(10000);
loop_offset->set_step(0.001);
loop_offset->set_suffix("sec");
loop_offset->set_tooltip_text(TTR("Loop offset (from beginning). Note that if BPM is set, this setting will be ignored."));
loop_offset->connect("value_changed", callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
loop_hb->add_child(loop_offset);
loop_offset_type = memnew(OptionButton);
loop_offset_type->add_item(TTR("Sec"));
loop_offset_type->add_item(TTR("Beats"));
loop_offset_type->connect("item_selected", callable_mp(this, &AudioStreamImportSettingsDialog::_update_loop_offset_type).unbind(1));
loop_offset_type->connect("item_selected", callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
loop_hb->add_child(loop_offset_type);
main_vbox->add_margin_child(TTR("Loop:"), loop_hb);

HBoxContainer *interactive_hb = memnew(HBoxContainer);
Expand All @@ -564,15 +604,16 @@ AudioStreamImportSettingsDialog::AudioStreamImportSettingsDialog() {
interactive_hb->add_child(bpm_edit);
interactive_hb->add_spacer();
beats_enabled = memnew(CheckBox);
beats_enabled->set_text(TTR("Beat Count:"));
beats_enabled->set_text(TTR("Length (beats):"));
beats_enabled->connect("toggled", callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
interactive_hb->add_child(beats_enabled);
beats_edit = memnew(SpinBox);
beats_edit->set_tooltip_text(TTR("Configure the amount of Beats used for music-aware looping. If zero, it will be autodetected from the length.\nIt is recommended to set this value (either manually or by clicking on a beat number in the preview) to ensure looping works properly."));
beats_edit->set_max(99999);
beats_edit->connect("value_changed", callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
interactive_hb->add_child(beats_edit);
bar_beats_label = memnew(Label(TTR("Bar Beats:")));
bar_beats_label = memnew(Label(TTR("Bar (beats):")));
interactive_hb->add_spacer();
interactive_hb->add_child(bar_beats_label);
bar_beats_edit = memnew(SpinBox);
bar_beats_edit->set_tooltip_text(TTR("Configure the Beats Per Bar. This used for music-aware transitions between AudioStreams."));
Expand Down
4 changes: 4 additions & 0 deletions editor/import/audio_stream_import_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "scene/audio/audio_stream_player.h"
#include "scene/gui/color_rect.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/option_button.h"
#include "scene/gui/spin_box.h"
#include "scene/resources/texture.h"

Expand All @@ -50,9 +51,11 @@ class AudioStreamImportSettingsDialog : public ConfirmationDialog {
Label *bar_beats_label = nullptr;
SpinBox *bar_beats_edit = nullptr;
CheckBox *loop = nullptr;
OptionButton *loop_offset_type = nullptr;
SpinBox *loop_offset = nullptr;
ColorRect *color_rect = nullptr;
Ref<AudioStream> stream;
Ref<AudioStream> stream_no_loop;
AudioStreamPlayer *_player = nullptr;
ColorRect *_preview = nullptr;
Control *_indicator = nullptr;
Expand Down Expand Up @@ -86,6 +89,7 @@ class AudioStreamImportSettingsDialog : public ConfirmationDialog {
void _settings_changed();

void _reimport();
void _update_loop_offset_type();

protected:
void _notification(int p_what);
Expand Down
7 changes: 6 additions & 1 deletion modules/minimp3/audio_stream_mp3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) {
if (samples_mixed) {
p_buffer[p_frames - todo] = AudioFrame(buf_frame[0], buf_frame[samples_mixed - 1]);
if (loop_fade_remaining < FADE_SIZE) {
p_buffer[p_frames - todo] += loop_fade[loop_fade_remaining] * (float(FADE_SIZE - loop_fade_remaining) / float(FADE_SIZE));
float c = (float(FADE_SIZE - loop_fade_remaining) / float(FADE_SIZE));
if (mp3_stream->loop_offset > 0) {
// Only fade-in if loop-offset > 0
p_buffer[p_frames - todo] *= 1.0 - c;
}
p_buffer[p_frames - todo] += loop_fade[loop_fade_remaining] * c;
loop_fade_remaining++;
}
--todo;
Expand Down
6 changes: 6 additions & 0 deletions modules/minimp3/resource_importer_mp3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ String ResourceImporterMP3::get_preset_name(int p_idx) const {

void ResourceImporterMP3::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "loop"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "loop_offset_type", PROPERTY_HINT_ENUM, "Seconds,Beats"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "loop_offset"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "bpm", PROPERTY_HINT_RANGE, "0,400,0.01,or_greater"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "beat_count", PROPERTY_HINT_RANGE, "0,512,or_greater"), 0));
Expand Down Expand Up @@ -118,10 +119,15 @@ Ref<AudioStreamMP3> ResourceImporterMP3::import_mp3(const String &p_path) {
Error ResourceImporterMP3::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
bool loop = p_options["loop"];
float loop_offset = p_options["loop_offset"];
int loop_offset_type = p_options["loop_offset_type"];
double bpm = p_options["bpm"];
float beat_count = p_options["beat_count"];
float bar_beats = p_options["bar_beats"];

if (loop_offset_type == 1) {
loop_offset *= (60.0 / bpm);
}

Ref<AudioStreamMP3> mp3_stream = import_mp3(p_source_file);
if (mp3_stream.is_null()) {
return ERR_CANT_OPEN;
Expand Down
7 changes: 6 additions & 1 deletion modules/vorbis/audio_stream_ogg_vorbis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ int AudioStreamPlaybackOggVorbis::_mix_internal(AudioFrame *p_buffer, int p_fram
if (loop_fade_remaining < FADE_SIZE) {
int to_fade = loop_fade_remaining + MIN(FADE_SIZE - loop_fade_remaining, mixed);
for (int i = loop_fade_remaining; i < to_fade; i++) {
buffer[i - loop_fade_remaining] += loop_fade[i] * (float(FADE_SIZE - i) / float(FADE_SIZE));
float c = (float(FADE_SIZE - i) / float(FADE_SIZE));
if (vorbis_stream->loop_offset > 0) {
// Only fade-in if loop-offset > 0
buffer[i - loop_fade_remaining] *= 1.0 - c;
}
buffer[i - loop_fade_remaining] += loop_fade[i] * c;
}
loop_fade_remaining = to_fade;
}
Expand Down
6 changes: 6 additions & 0 deletions modules/vorbis/resource_importer_ogg_vorbis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ String ResourceImporterOggVorbis::get_preset_name(int p_idx) const {

void ResourceImporterOggVorbis::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "loop"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "loop_offset_type", PROPERTY_HINT_ENUM, "Seconds,Beats"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "loop_offset"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "bpm", PROPERTY_HINT_RANGE, "0,400,0.01,or_greater"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "beat_count", PROPERTY_HINT_RANGE, "0,512,or_greater"), 0));
Expand All @@ -98,10 +99,15 @@ void ResourceImporterOggVorbis::show_advanced_options(const String &p_path) {
Error ResourceImporterOggVorbis::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
bool loop = p_options["loop"];
double loop_offset = p_options["loop_offset"];
int loop_offset_type = p_options["loop_offset_type"];
double bpm = p_options["bpm"];
int beat_count = p_options["beat_count"];
int bar_beats = p_options["bar_beats"];

if (loop_offset_type == 1) {
loop_offset *= (60.0 / bpm);
}

Ref<AudioStreamOggVorbis> ogg_vorbis_stream = load_from_file(p_source_file);
if (ogg_vorbis_stream.is_null()) {
return ERR_CANT_OPEN;
Expand Down

0 comments on commit 89b20b8

Please sign in to comment.