Implement BPM support

Based on #62896, only implements the BPM support part.

* Implements BPM support in the AudioStreamOGG/MP3 importers.
* Can select BPM/Bar Size and total beats in a song file, as well as edit looping points.
* Looping is now BPM aware
* Added a special importer UI for configuring this.
* Added a special preview showing the audio waveform as well as the playback position in the resource picker.
* Renamed `AudioStream::instance` to `instantiate` for correctness.
This commit is contained in:
reduz 2022-07-21 01:00:58 +02:00 committed by Juan Linietsky
parent 976cb7ea9f
commit d1ddee2258
41 changed files with 1548 additions and 438 deletions

View file

@ -83,6 +83,10 @@ int AudioStreamPlayback::mix(AudioFrame *p_buffer, float p_rate_scale, int p_fra
return 0;
}
void AudioStreamPlayback::tag_used_streams() {
GDVIRTUAL_CALL(_tag_used_streams);
}
void AudioStreamPlayback::_bind_methods() {
GDVIRTUAL_BIND(_start, "from_pos")
GDVIRTUAL_BIND(_stop)
@ -91,6 +95,7 @@ void AudioStreamPlayback::_bind_methods() {
GDVIRTUAL_BIND(_get_playback_position)
GDVIRTUAL_BIND(_seek, "position")
GDVIRTUAL_BIND(_mix, "buffer", "rate_scale", "frames");
GDVIRTUAL_BIND(_tag_used_streams);
}
//////////////////////////////
@ -187,9 +192,9 @@ int AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale,
////////////////////////////////
Ref<AudioStreamPlayback> AudioStream::instance_playback() {
Ref<AudioStreamPlayback> AudioStream::instantiate_playback() {
Ref<AudioStreamPlayback> ret;
if (GDVIRTUAL_CALL(_instance_playback, ret)) {
if (GDVIRTUAL_CALL(_instantiate_playback, ret)) {
return ret;
}
ERR_FAIL_V_MSG(Ref<AudioStreamPlayback>(), "Method must be implemented!");
@ -218,19 +223,74 @@ bool AudioStream::is_monophonic() const {
return true;
}
double AudioStream::get_bpm() const {
double ret = 0;
if (GDVIRTUAL_CALL(_get_bpm, ret)) {
return ret;
}
return 0;
}
bool AudioStream::has_loop() const {
bool ret = 0;
if (GDVIRTUAL_CALL(_has_loop, ret)) {
return ret;
}
return 0;
}
int AudioStream::get_bar_beats() const {
int ret = 0;
if (GDVIRTUAL_CALL(_get_bar_beats, ret)) {
return ret;
}
return 0;
}
int AudioStream::get_beat_count() const {
int ret = 0;
if (GDVIRTUAL_CALL(_get_beat_count, ret)) {
return ret;
}
return 0;
}
void AudioStream::tag_used(float p_offset) {
if (tagged_frame != AudioServer::get_singleton()->get_mixed_frames()) {
offset_count = 0;
tagged_frame = AudioServer::get_singleton()->get_mixed_frames();
}
if (offset_count < MAX_TAGGED_OFFSETS) {
tagged_offsets[offset_count++] = p_offset;
}
}
uint64_t AudioStream::get_tagged_frame() const {
return tagged_frame;
}
uint32_t AudioStream::get_tagged_frame_count() const {
return offset_count;
}
float AudioStream::get_tagged_frame_offset(int p_index) const {
ERR_FAIL_INDEX_V(p_index, MAX_TAGGED_OFFSETS, 0);
return tagged_offsets[p_index];
}
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("instance_playback"), &AudioStream::instance_playback);
GDVIRTUAL_BIND(_instance_playback);
ClassDB::bind_method(D_METHOD("instantiate_playback"), &AudioStream::instantiate_playback);
GDVIRTUAL_BIND(_instantiate_playback);
GDVIRTUAL_BIND(_get_stream_name);
GDVIRTUAL_BIND(_get_length);
GDVIRTUAL_BIND(_is_monophonic);
GDVIRTUAL_BIND(_get_bpm)
GDVIRTUAL_BIND(_get_beat_count)
}
////////////////////////////////
Ref<AudioStreamPlayback> AudioStreamMicrophone::instance_playback() {
Ref<AudioStreamPlayback> AudioStreamMicrophone::instantiate_playback() {
Ref<AudioStreamPlaybackMicrophone> playback;
playback.instantiate();
@ -363,6 +423,10 @@ void AudioStreamPlaybackMicrophone::seek(float p_time) {
// Can't seek a microphone input
}
void AudioStreamPlaybackMicrophone::tag_used_streams() {
microphone->tag_used(0);
}
AudioStreamPlaybackMicrophone::~AudioStreamPlaybackMicrophone() {
microphone->playbacks.erase(this);
stop();
@ -490,7 +554,7 @@ Ref<AudioStreamPlayback> AudioStreamRandomizer::instance_playback_random() {
for (PoolEntry &entry : local_pool) {
cumulative_weight += entry.weight;
if (cumulative_weight > chosen_cumulative_weight) {
playback->playback = entry.stream->instance_playback();
playback->playback = entry.stream->instantiate_playback();
last_playback = entry.stream;
break;
}
@ -498,7 +562,7 @@ Ref<AudioStreamPlayback> AudioStreamRandomizer::instance_playback_random() {
if (playback->playback.is_null()) {
// This indicates a floating point error. Take the last element.
last_playback = local_pool[local_pool.size() - 1].stream;
playback->playback = local_pool.write[local_pool.size() - 1].stream->instance_playback();
playback->playback = local_pool.write[local_pool.size() - 1].stream->instantiate_playback();
}
return playback;
}
@ -532,14 +596,14 @@ Ref<AudioStreamPlayback> AudioStreamRandomizer::instance_playback_no_repeats() {
cumulative_weight += entry.weight;
if (cumulative_weight > chosen_cumulative_weight) {
last_playback = entry.stream;
playback->playback = entry.stream->instance_playback();
playback->playback = entry.stream->instantiate_playback();
break;
}
}
if (playback->playback.is_null()) {
// This indicates a floating point error. Take the last element.
last_playback = local_pool[local_pool.size() - 1].stream;
playback->playback = local_pool.write[local_pool.size() - 1].stream->instance_playback();
playback->playback = local_pool.write[local_pool.size() - 1].stream->instantiate_playback();
}
return playback;
}
@ -568,7 +632,7 @@ Ref<AudioStreamPlayback> AudioStreamRandomizer::instance_playback_sequential() {
for (Ref<AudioStream> &entry : local_pool) {
if (found_last_stream) {
last_playback = entry;
playback->playback = entry->instance_playback();
playback->playback = entry->instantiate_playback();
break;
}
if (entry == last_playback) {
@ -578,12 +642,12 @@ Ref<AudioStreamPlayback> AudioStreamRandomizer::instance_playback_sequential() {
if (playback->playback.is_null()) {
// Wrap around
last_playback = local_pool[0];
playback->playback = local_pool.write[0]->instance_playback();
playback->playback = local_pool.write[0]->instantiate_playback();
}
return playback;
}
Ref<AudioStreamPlayback> AudioStreamRandomizer::instance_playback() {
Ref<AudioStreamPlayback> AudioStreamRandomizer::instantiate_playback() {
switch (playback_mode) {
case PLAYBACK_RANDOM:
return instance_playback_random();
@ -762,6 +826,14 @@ void AudioStreamPlaybackRandomizer::seek(float p_time) {
}
}
void AudioStreamPlaybackRandomizer::tag_used_streams() {
Ref<AudioStreamPlayback> p = playing; // Thread safety
if (p.is_valid()) {
p->tag_used_streams();
}
randomizer->tag_used(0);
}
int AudioStreamPlaybackRandomizer::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) {
if (playing.is_valid()) {
return playing->mix(p_buffer, p_rate_scale * pitch_scale, p_frames);