Skip to content

Commit 3661eff

Browse files
committed
add multiple instrument slice support in godot
1 parent c71fd09 commit 3661eff

12 files changed

Lines changed: 234 additions & 65 deletions

platforms/godot/instrument.cpp

Lines changed: 79 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ using ::godot::PropertyHint;
2323
using ::godot::PropertyInfo;
2424
using ::godot::Ref;
2525
using ::godot::StringName;
26+
using ::godot::TypedArray;
2627
using ::godot::Variant;
2728

2829
#define BARELY_BIND_GODOT_ENUM_VALUE(EnumType, Name, Value) \
@@ -109,13 +110,20 @@ void BarelyInstrument::set_note_on(float pitch, float gain, float pitch_shift) {
109110
}
110111
}
111112

112-
void BarelyInstrument::set_slice(const Ref<BarelySliceResource>& slice) {
113-
if (slice_.is_valid()) {
114-
slice_->disconnect("slice_changed", Callable(this, "_on_slice_changed"));
113+
void BarelyInstrument::set_slices(const TypedArray<Ref<BarelySliceResource>>& slices) {
114+
for (int i = 0; i < slices_.size(); ++i) {
115+
Ref<BarelySliceResource> slice = slices_[i];
116+
if (slice.is_valid()) {
117+
slice->disconnect("slice_changed", Callable(this, "_on_slice_changed"));
118+
}
115119
}
116-
slice_ = slice;
117-
if (slice_.is_valid()) {
118-
slice_->connect("slice_changed", Callable(this, "_on_slice_changed"), Object::CONNECT_DEFERRED);
120+
slices_ = slices;
121+
for (int i = 0; i < slices_.size(); ++i) {
122+
Ref<BarelySliceResource> slice = slices_[i];
123+
if (slice.is_valid()) {
124+
slice->connect("slice_changed", Callable(this, "_on_slice_changed"),
125+
Object::CONNECT_DEFERRED);
126+
}
119127
}
120128
_on_slice_changed();
121129
}
@@ -126,8 +134,8 @@ void BarelyInstrument::_bind_methods() {
126134
ClassDB::bind_method(D_METHOD("set_note_on", "pitch", "gain", "pitch_shift"),
127135
&BarelyInstrument::set_note_on, DEFVAL(1.0f), DEFVAL(0.0f));
128136
ClassDB::bind_method(D_METHOD("is_note_on", "pitch"), &BarelyInstrument::is_note_on);
129-
ClassDB::bind_method(D_METHOD("set_slice", "slice"), &BarelyInstrument::set_slice);
130-
ClassDB::bind_method(D_METHOD("get_slice"), &BarelyInstrument::get_slice);
137+
ClassDB::bind_method(D_METHOD("set_slices", "slices"), &BarelyInstrument::set_slices);
138+
ClassDB::bind_method(D_METHOD("get_slices"), &BarelyInstrument::get_slices);
131139

132140
ClassDB::bind_method(D_METHOD("_on_slice_changed"), &BarelyInstrument::_on_slice_changed);
133141

@@ -168,9 +176,9 @@ void BarelyInstrument::_bind_methods() {
168176
ADD_PROPERTY(PropertyInfo(Variant::INT, "slice_mode", PropertyHint::PROPERTY_HINT_ENUM,
169177
"Sustain,Loop,Once"),
170178
"set_slice_mode", "get_slice_mode");
171-
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "slice", PropertyHint::PROPERTY_HINT_RESOURCE_TYPE,
179+
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "slices", PropertyHint::PROPERTY_HINT_ARRAY_TYPE,
172180
"BarelySliceResource"),
173-
"set_slice", "get_slice");
181+
"set_slices", "get_slices");
174182

175183
ADD_PROPERTY(
176184
PropertyInfo(Variant::FLOAT, "osc_mix", PropertyHint::PROPERTY_HINT_RANGE, "0,1,0.01"),
@@ -228,71 +236,83 @@ void BarelyInstrument::_bind_methods() {
228236
}
229237

230238
void BarelyInstrument::_on_slice_changed() {
231-
if (!slice_buffer_.empty()) {
239+
if (!slice_buffers_.empty()) {
232240
BarelyInstrument_SetSampleData(BarelyEngine::get_singleton()->get(), instrument_id_, nullptr,
233241
0);
234-
slice_buffer_.clear();
235-
}
236-
237-
if (slice_.is_null()) {
238-
return;
242+
slice_buffers_.clear();
239243
}
240244

241-
Ref<AudioStreamWAV> stream = slice_->get_stream();
242-
if (stream.is_null()) {
245+
if (slices_.is_empty()) {
243246
return;
244247
}
245248

246-
const auto& data = stream->get_data();
247-
const uint8_t* bytes = data.ptr();
248-
const int byte_count = data.size();
249-
250-
const auto format = stream->get_format();
251-
const int32_t sample_rate = stream->get_mix_rate();
249+
std::vector<BarelySlice> sample_data(slices_.size(), BarelySlice{nullptr, 0, 0, 0.0f});
250+
slice_buffers_.resize(slices_.size());
252251

253-
if (format == AudioStreamWAV::FORMAT_16_BITS) {
254-
const int16_t* pcm_bytes = (int16_t*)bytes;
255-
const int count = byte_count / sizeof(int16_t);
256-
slice_buffer_.resize(count);
257-
for (int i = 0; i < count; ++i) {
258-
static constexpr float kMaxSample = 32768.0f;
259-
slice_buffer_[i] = pcm_bytes[i] / kMaxSample;
252+
for (int i = 0; i < slices_.size(); ++i) {
253+
const Ref<BarelySliceResource>& slice = slices_[i];
254+
if (slice.is_null()) {
255+
continue;
260256
}
261-
} else if (format == AudioStreamWAV::FORMAT_8_BITS) {
262-
slice_buffer_.resize(byte_count);
263-
for (int i = 0; i < byte_count; ++i) {
264-
static constexpr float kMaxSample = 128.0f;
265-
slice_buffer_[i] = (bytes[i] - 128) / kMaxSample;
266-
}
267-
} else if (format == AudioStreamWAV::FORMAT_QOA) {
268-
Ref<AudioStreamPlayback> playback = stream->instantiate_playback();
269-
if (!playback.is_valid()) {
270-
return;
271-
}
272-
playback->start(0.0);
273257

274-
const float length = stream->get_length();
275-
slice_buffer_.reserve(static_cast<int32_t>(length * static_cast<float>(sample_rate)));
258+
Ref<AudioStreamWAV> stream = slice->get_stream();
259+
if (stream.is_null()) {
260+
continue;
261+
}
276262

277-
static constexpr int kQoaFrameCount = 512;
278-
while (playback->is_playing()) {
279-
const auto frames = playback->mix_audio(1.0f, kQoaFrameCount);
280-
if (frames.size() == 0) {
281-
break;
263+
const auto& data = stream->get_data();
264+
const uint8_t* bytes = data.ptr();
265+
const int byte_count = data.size();
266+
267+
const auto format = stream->get_format();
268+
const int32_t sample_rate = stream->get_mix_rate();
269+
270+
auto& samples = slice_buffers_[i];
271+
if (format == AudioStreamWAV::FORMAT_16_BITS) {
272+
const int16_t* pcm_bytes = reinterpret_cast<const int16_t*>(bytes);
273+
const int count = byte_count / sizeof(int16_t);
274+
samples.resize(count);
275+
for (int i = 0; i < count; ++i) {
276+
static constexpr float kMaxSample = 32768.0f;
277+
samples[i] = pcm_bytes[i] / kMaxSample;
282278
}
283-
for (int i = 0; i < static_cast<int>(frames.size()); ++i) {
284-
const auto& frame = frames.ptr()[i];
285-
slice_buffer_.push_back(0.5f * (frame.x + frame.y));
279+
} else if (format == AudioStreamWAV::FORMAT_8_BITS) {
280+
samples.resize(byte_count);
281+
for (int i = 0; i < byte_count; ++i) {
282+
static constexpr float kMaxSample = 128.0f;
283+
samples[i] = (bytes[i] - 128) / kMaxSample;
286284
}
285+
} else if (format == AudioStreamWAV::FORMAT_QOA) {
286+
Ref<AudioStreamPlayback> playback = stream->instantiate_playback();
287+
if (!playback.is_valid()) {
288+
continue;
289+
}
290+
playback->start(0.0);
291+
292+
const float length = stream->get_length();
293+
samples.reserve(static_cast<int32_t>(length * static_cast<float>(sample_rate)));
294+
295+
static constexpr int kQoaFrameCount = 512;
296+
while (playback->is_playing()) {
297+
const auto frames = playback->mix_audio(1.0f, kQoaFrameCount);
298+
if (frames.size() == 0) {
299+
break;
300+
}
301+
for (int i = 0; i < static_cast<int>(frames.size()); ++i) {
302+
const auto& frame = frames.ptr()[i];
303+
samples.push_back(0.5f * (frame.x + frame.y));
304+
}
305+
}
306+
} else {
307+
continue; // TODO(#181): Support all formats.
287308
}
288-
} else {
289-
return; // TODO(#181): Support all formats.
309+
310+
sample_data[i] = {samples.data(), static_cast<int32_t>(samples.size()), sample_rate,
311+
slice->get_root_pitch()};
290312
}
291313

292-
const BarelySlice sample_data{slice_buffer_.data(), static_cast<int32_t>(slice_buffer_.size()),
293-
sample_rate, slice_->get_root_pitch()};
294-
BarelyInstrument_SetSampleData(BarelyEngine::get_singleton()->get(), instrument_id_, &sample_data,
295-
1);
314+
BarelyInstrument_SetSampleData(BarelyEngine::get_singleton()->get(), instrument_id_,
315+
sample_data.data(), static_cast<int32_t>(sample_data.size()));
296316
}
297317

298318
} // namespace barely::godot

platforms/godot/instrument.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "godot_cpp/classes/node.hpp"
1313
#include "godot_cpp/classes/resource.hpp"
1414
#include "godot_cpp/classes/wrapped.hpp"
15+
#include "godot_cpp/variant/typed_array.hpp"
1516

1617
namespace barely::godot {
1718

@@ -82,19 +83,19 @@ class BarelyInstrument : public ::godot::Node {
8283
void set_note_on(float pitch, float gain = 1.0f, float pitch_shift = 0.0f);
8384
bool is_note_on(float pitch) const { return pitches_.contains(pitch); }
8485

85-
void set_slice(const ::godot::Ref<BarelySliceResource>& slice);
86-
::godot::Ref<BarelySliceResource> get_slice() const { return slice_; }
86+
void set_slices(const ::godot::TypedArray<::godot::Ref<BarelySliceResource>>& slices);
87+
::godot::TypedArray<::godot::Ref<BarelySliceResource>> get_slices() const { return slices_; };
8788

8889
private:
8990
GDCLASS(BarelyInstrument, ::godot::Node);
9091
static void _bind_methods();
9192
void _on_slice_changed();
9293

9394
uint32_t instrument_id_ = 0;
94-
::godot::Ref<BarelySliceResource> slice_;
95+
::godot::TypedArray<::godot::Ref<BarelySliceResource>> slices_;
9596
// TODO(#181): Remove heap allocations.
97+
std::vector<std::vector<float>> slice_buffers_;
9698
std::unordered_set<float> pitches_;
97-
std::vector<float> slice_buffer_;
9899

99100
BARELY_GODOT_INSTRUMENT_CONTROLS(BARELY_DECLARE_GODOT_INSTRUMENT_CONTROL);
100101
};
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://b77ymswijvykk"
6+
path="res://.godot/imported/basic_hihat_closed.wav-3aa5740791ec2b0bf25a61375038bb2d.sample"
7+
8+
[deps]
9+
10+
source_file="res://addons/barelymusician/demo/data/basic_hihat_closed.wav"
11+
dest_files=["res://.godot/imported/basic_hihat_closed.wav-3aa5740791ec2b0bf25a61375038bb2d.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
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://cxe0id7oco4je"
6+
path="res://.godot/imported/basic_hihat_open.wav-9932b9c1e6334ed7ff2c79113e4aa0b3.sample"
7+
8+
[deps]
9+
10+
source_file="res://addons/barelymusician/demo/data/basic_hihat_open.wav"
11+
dest_files=["res://.godot/imported/basic_hihat_open.wav-9932b9c1e6334ed7ff2c79113e4aa0b3.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
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://ckwfallmnbmj1"
6+
path="res://.godot/imported/basic_kick.wav-3d589c1c6558db384d894418033a571e.sample"
7+
8+
[deps]
9+
10+
source_file="res://addons/barelymusician/demo/data/basic_kick.wav"
11+
dest_files=["res://.godot/imported/basic_kick.wav-3d589c1c6558db384d894418033a571e.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
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://dflgt3eeqb7s1"
6+
path="res://.godot/imported/basic_snare.wav-960ab6f854590551ec47ce2cfb95d222.sample"
7+
8+
[deps]
9+
10+
source_file="res://addons/barelymusician/demo/data/basic_snare.wav"
11+
dest_files=["res://.godot/imported/basic_snare.wav-960ab6f854590551ec47ce2cfb95d222.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

0 commit comments

Comments
 (0)