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
912namespace barely ::godot {
1013
14+ using ::godot::AudioFrame;
15+ using ::godot::AudioStreamPlayback;
16+ using ::godot::AudioStreamWAV;
17+ using ::godot::Callable;
1118using ::godot::ClassDB;
1219using ::godot::D_METHOD;
1320using ::godot::MethodInfo;
21+ using ::godot::Object;
1422using ::godot::PropertyHint;
1523using ::godot::PropertyInfo;
24+ using ::godot::Ref;
1625using ::godot::StringName;
1726using ::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
2769BarelyInstrument::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+
68121void 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
0 commit comments