Skip to content

Commit 0c8f0ed

Browse files
committed
add godot instrument sample data support
1 parent e60e69f commit 0c8f0ed

File tree

7 files changed

+188
-5
lines changed

7 files changed

+188
-5
lines changed

platforms/godot/godot.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ void initialize_barelymusiciangodot(ModuleInitializationLevel level) {
1616
ClassDB::register_internal_class<barely::godot::BarelyEngineNode>();
1717
ClassDB::register_class<barely::godot::BarelyAudioStream>();
1818
ClassDB::register_class<barely::godot::BarelyEngine>();
19+
ClassDB::register_class<barely::godot::BarelySliceResource>();
1920
ClassDB::register_class<barely::godot::BarelyInstrument>();
2021
::godot::Engine::get_singleton()->register_singleton("BarelyEngine",
2122
memnew(barely::godot::BarelyEngine));

platforms/godot/instrument.cpp

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,25 @@
33
#include <barelymusician.h>
44

55
#include "godot/engine.h"
6+
#include "godot_cpp/classes/audio_frame.hpp"
7+
#include "godot_cpp/classes/audio_stream_playback.hpp"
8+
#include "godot_cpp/classes/audio_stream_wav.hpp"
69
#include "godot_cpp/core/class_db.hpp"
710
#include "godot_cpp/core/object.hpp"
811

912
namespace barely::godot {
1013

14+
using ::godot::AudioFrame;
15+
using ::godot::AudioStreamPlayback;
16+
using ::godot::AudioStreamWAV;
17+
using ::godot::Callable;
1118
using ::godot::ClassDB;
1219
using ::godot::D_METHOD;
1320
using ::godot::MethodInfo;
21+
using ::godot::Object;
1422
using ::godot::PropertyHint;
1523
using ::godot::PropertyInfo;
24+
using ::godot::Ref;
1625
using ::godot::StringName;
1726
using ::godot::Variant;
1827

@@ -23,6 +32,39 @@ using ::godot::Variant;
2332
ClassDB::bind_method(D_METHOD(BARELY_STR(set_##name), #name), &BarelyInstrument::set_##name); \
2433
ClassDB::bind_method(D_METHOD(BARELY_STR(get_##name)), &BarelyInstrument::get_##name);
2534
#define BARELY_SET_DEFAULT_GODOT_INSTRUMENT_CONTROL(Name, name, type, default) set_##name(default);
35+
#define BARELY_SET_GODOT_INSTRUMENT_CONTROL(Name, name, type, default) set_##name(name##_);
36+
37+
void BarelySliceResource::set_root_pitch(float root_pitch) {
38+
if (root_pitch_ != root_pitch) {
39+
root_pitch_ = root_pitch;
40+
emit_signal("slice_changed");
41+
}
42+
}
43+
44+
void BarelySliceResource::set_stream(const Ref<AudioStreamWAV>& stream) {
45+
if (stream_ != stream) {
46+
stream_ = stream;
47+
emit_signal("slice_changed");
48+
}
49+
}
50+
51+
void BarelySliceResource::_bind_methods() {
52+
ClassDB::bind_method(D_METHOD("set_stream", "stream"), &BarelySliceResource::set_stream);
53+
ClassDB::bind_method(D_METHOD("get_stream"), &BarelySliceResource::get_stream);
54+
55+
ClassDB::bind_method(D_METHOD("set_root_pitch", "root_pitch"),
56+
&BarelySliceResource::set_root_pitch);
57+
ClassDB::bind_method(D_METHOD("get_root_pitch"), &BarelySliceResource::get_root_pitch);
58+
59+
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PropertyHint::PROPERTY_HINT_RESOURCE_TYPE,
60+
"AudioStreamWAV"),
61+
"set_stream", "get_stream");
62+
ADD_PROPERTY(
63+
PropertyInfo(Variant::FLOAT, "root_pitch", PropertyHint::PROPERTY_HINT_RANGE, "-4,4,0.01"),
64+
"set_root_pitch", "get_root_pitch");
65+
66+
ADD_SIGNAL(MethodInfo("slice_changed"));
67+
}
2668

2769
BarelyInstrument::BarelyInstrument() {
2870
::BarelyEngine* engine = BarelyEngine::get_singleton()->get();
@@ -65,12 +107,27 @@ bool BarelyInstrument::is_note_on(float pitch) const {
65107
return is_note_on;
66108
}
67109

110+
void BarelyInstrument::set_slice(const Ref<BarelySliceResource>& slice) {
111+
if (slice_.is_valid()) {
112+
slice_->disconnect("slice_changed", Callable(this, "_on_slice_changed"));
113+
}
114+
slice_ = slice;
115+
if (slice_.is_valid()) {
116+
slice_->connect("slice_changed", Callable(this, "_on_slice_changed"), Object::CONNECT_DEFERRED);
117+
}
118+
_on_slice_changed();
119+
}
120+
68121
void BarelyInstrument::_bind_methods() {
69122
ClassDB::bind_method(D_METHOD("set_all_notes_off"), &BarelyInstrument::set_all_notes_off);
70123
ClassDB::bind_method(D_METHOD("set_note_off", "pitch"), &BarelyInstrument::set_note_off);
71124
ClassDB::bind_method(D_METHOD("set_note_on", "pitch", "gain", "pitch_shift"),
72125
&BarelyInstrument::set_note_on, DEFVAL(1.0f), DEFVAL(0.0f));
73126
ClassDB::bind_method(D_METHOD("is_note_on", "pitch"), &BarelyInstrument::is_note_on);
127+
ClassDB::bind_method(D_METHOD("set_slice", "slice"), &BarelyInstrument::set_slice);
128+
ClassDB::bind_method(D_METHOD("get_slice"), &BarelyInstrument::get_slice);
129+
130+
ClassDB::bind_method(D_METHOD("_on_slice_changed"), &BarelyInstrument::_on_slice_changed);
74131

75132
BARELY_BIND_GODOT_ENUM_VALUE(ArpMode, None, "ARP_MODE_NONE");
76133
BARELY_BIND_GODOT_ENUM_VALUE(ArpMode, Up, "ARP_MODE_UP");
@@ -109,9 +166,14 @@ void BarelyInstrument::_bind_methods() {
109166
ADD_PROPERTY(
110167
PropertyInfo(Variant::FLOAT, "release", PropertyHint::PROPERTY_HINT_RANGE, "0,8,0.01"),
111168
"set_release", "get_release");
169+
112170
ADD_PROPERTY(PropertyInfo(Variant::INT, "slice_mode", PropertyHint::PROPERTY_HINT_ENUM,
113171
"Sustain,Loop,Once"),
114172
"set_slice_mode", "get_slice_mode");
173+
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "slice", PropertyHint::PROPERTY_HINT_RESOURCE_TYPE,
174+
"BarelySliceResource"),
175+
"set_slice", "get_slice");
176+
115177
ADD_PROPERTY(
116178
PropertyInfo(Variant::FLOAT, "osc_mix", PropertyHint::PROPERTY_HINT_RANGE, "0,1,0.01"),
117179
"set_osc_mix", "get_osc_mix");
@@ -190,4 +252,72 @@ void BarelyInstrument::_handle_note_event(BarelyEventType type, float pitch) {
190252
}
191253
}
192254

255+
void BarelyInstrument::_on_slice_changed() {
256+
if (!slice_buffer_.empty()) {
257+
BarelyInstrument_SetSampleData(BarelyEngine::get_singleton()->get(), instrument_id_, nullptr,
258+
0);
259+
slice_buffer_.clear(); // TODO(#181): This is not thread safe although should be ok for now.
260+
}
261+
262+
if (slice_.is_null()) {
263+
return;
264+
}
265+
266+
Ref<AudioStreamWAV> stream = slice_->get_stream();
267+
if (stream.is_null()) {
268+
return;
269+
}
270+
271+
const auto& data = stream->get_data();
272+
const uint8_t* bytes = data.ptr();
273+
const int byte_count = data.size();
274+
275+
const auto format = stream->get_format();
276+
const int32_t sample_rate = stream->get_mix_rate();
277+
278+
if (format == AudioStreamWAV::FORMAT_16_BITS) {
279+
const int16_t* pcm_bytes = (int16_t*)bytes;
280+
const int count = byte_count / sizeof(int16_t);
281+
slice_buffer_.resize(count);
282+
for (int i = 0; i < count; ++i) {
283+
static constexpr float kMaxSample = 32768.0f;
284+
slice_buffer_[i] = pcm_bytes[i] / kMaxSample;
285+
}
286+
} else if (format == AudioStreamWAV::FORMAT_8_BITS) {
287+
slice_buffer_.resize(byte_count);
288+
for (int i = 0; i < byte_count; ++i) {
289+
static constexpr float kMaxSample = 128.0f;
290+
slice_buffer_[i] = (bytes[i] - 128) / kMaxSample;
291+
}
292+
} else if (format == AudioStreamWAV::FORMAT_QOA) {
293+
Ref<AudioStreamPlayback> playback = stream->instantiate_playback();
294+
if (!playback.is_valid()) {
295+
return;
296+
}
297+
playback->start(0.0);
298+
299+
const float length = stream->get_length();
300+
slice_buffer_.reserve(static_cast<int32_t>(length * static_cast<float>(sample_rate)));
301+
302+
static constexpr int kQoaFrameCount = 512;
303+
while (playback->is_playing()) {
304+
const auto frames = playback->mix_audio(1.0f, kQoaFrameCount);
305+
if (frames.size() == 0) {
306+
break;
307+
}
308+
for (int i = 0; i < static_cast<int>(frames.size()); ++i) {
309+
const auto& frame = frames.ptr()[i];
310+
slice_buffer_.push_back(0.5f * (frame.x + frame.y));
311+
}
312+
}
313+
} else {
314+
return; // TODO(#181): Support all formats.
315+
}
316+
317+
const BarelySlice sample_data{slice_buffer_.data(), slice_buffer_.size(), sample_rate,
318+
slice_->get_root_pitch()};
319+
BarelyInstrument_SetSampleData(BarelyEngine::get_singleton()->get(), instrument_id_, &sample_data,
320+
1);
321+
}
322+
193323
} // namespace barely::godot

platforms/godot/instrument.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <cstdint>
77

88
#include "godot/engine.h"
9+
#include "godot_cpp/classes/audio_stream_wav.hpp"
910
#include "godot_cpp/classes/node.hpp"
1011
#include "godot_cpp/classes/wrapped.hpp"
1112

@@ -54,6 +55,22 @@ namespace barely::godot {
5455
} \
5556
type get_##name() const { return name##_; }
5657

58+
class BarelySliceResource : public ::godot::Resource {
59+
public:
60+
void set_stream(const ::godot::Ref<::godot::AudioStreamWAV>& stream);
61+
void set_root_pitch(float root_pitch);
62+
63+
::godot::Ref<::godot::AudioStreamWAV> get_stream() const { return stream_; }
64+
float get_root_pitch() const { return root_pitch_; }
65+
66+
private:
67+
GDCLASS(BarelySliceResource, ::godot::Resource);
68+
static void _bind_methods();
69+
70+
::godot::Ref<::godot::AudioStreamWAV> stream_;
71+
float root_pitch_ = 0.0f;
72+
};
73+
5774
class BarelyInstrument : public ::godot::Node {
5875
public:
5976
BarelyInstrument();
@@ -64,14 +81,20 @@ class BarelyInstrument : public ::godot::Node {
6481
void set_note_on(float pitch, float gain = 1.0f, float pitch_shift = 0.0f);
6582
bool is_note_on(float pitch) const;
6683

84+
void set_slice(const ::godot::Ref<BarelySliceResource>& slice);
85+
::godot::Ref<BarelySliceResource> get_slice() const { return slice_; }
86+
6787
private:
6888
GDCLASS(BarelyInstrument, ::godot::Node);
6989
static void _bind_methods();
7090

7191
static void _note_event_callback(BarelyEventType type, float pitch, void* user_data);
7292
void _handle_note_event(BarelyEventType type, float pitch);
93+
void _on_slice_changed();
7394

7495
uint32_t instrument_id_ = 0;
96+
::godot::Ref<BarelySliceResource> slice_;
97+
std::vector<float> slice_buffer_; // TODO(#181): Remove heap allocation.
7598

7699
BARELY_GODOT_INSTRUMENT_CONTROLS(BARELY_DECLARE_GODOT_INSTRUMENT_CONTROL);
77100
};
141 KB
Binary file not shown.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[remap]
2+
3+
importer="wav"
4+
type="AudioStreamWAV"
5+
uid="uid://vvwdk6lors0g"
6+
path="res://.godot/imported/sample.wav-6b134728960717e0b3e9490ee37cba43.sample"
7+
8+
[deps]
9+
10+
source_file="res://demo/data/sample.wav"
11+
dest_files=["res://.godot/imported/sample.wav-6b134728960717e0b3e9490ee37cba43.sample"]
12+
13+
[params]
14+
15+
force/8_bit=false
16+
force/mono=false
17+
force/max_rate=false
18+
force/max_rate_hz=44100
19+
edit/trim=false
20+
edit/normalize=false
21+
edit/loop_mode=0
22+
edit/loop_begin=0
23+
edit/loop_end=-1
24+
compress/mode=2

platforms/godot/project/demo/instrument_demo.gd

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@ func _ready():
1717
BarelyEngine.delay_feedback = 0.2
1818
BarelyEngine.delay_ping_pong = 0.5
1919
BarelyEngine.reverb_room_size = 0.4
20-
#
20+
#
2121
instrument.note_on.connect(_on_note_on)
2222
instrument.note_off.connect(_on_note_off)
23-
23+
2424
audioStreamPlayer.stream = BarelyAudioStream.new()
2525
audioStreamPlayer.play()
26-
2726

2827
func _input(event):
2928
if event is InputEventKey and event.pressed:
@@ -65,9 +64,9 @@ func _unhandled_input(event):
6564
if active_notes.has(key_string):
6665
instrument.set_note_off(active_notes[key_string])
6766
active_notes.erase(key_string)
68-
67+
6968
func _on_note_on(pitch):
7069
print("NoteOn(%.2f)\n" % pitch)
71-
70+
7271
func _on_note_off(pitch):
7372
print("NoteOff(%.2f)\n" % pitch)

platforms/godot/project/demo/instrument_demo.tscn

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
[gd_scene format=3 uid="uid://fm3yw7k5t227"]
22

33
[ext_resource type="Script" uid="uid://cx2ediq5nfg22" path="res://demo/instrument_demo.gd" id="1"]
4+
[ext_resource type="AudioStream" uid="uid://vvwdk6lors0g" path="res://demo/data/sample.wav" id="2_sv68r"]
5+
6+
[sub_resource type="BarelySliceResource" id="BarelySliceResource_sv68r"]
7+
stream = ExtResource("2_sv68r")
48

59
[sub_resource type="LabelSettings" id="LabelSettings_8q8q8"]
610

@@ -9,6 +13,8 @@ script = ExtResource("1")
913

1014
[node name="Instrument" type="BarelyInstrument" parent="." unique_id=1430736698]
1115
gain = 0.9
16+
slice_mode = 1
17+
slice = SubResource("BarelySliceResource_sv68r")
1218
osc_shape = 0.2
1319
delay_send = 0.1
1420
reverb_send = 0.5

0 commit comments

Comments
 (0)