diff --git a/doc_classes/SiONDriver.xml b/doc_classes/SiONDriver.xml index 8995942..88d5566 100644 --- a/doc_classes/SiONDriver.xml +++ b/doc_classes/SiONDriver.xml @@ -193,11 +193,28 @@ Executes a note on event using the given voice. Returns the affected, or newly created, track (see also [method SiMMLTrack.get_track_id]). - [param length], [param delay], and [param quantize] must be in 1/16th of a beat. - Use [param disposable] to control whether the track can be freed after being used. Making a track non-disposable must be treated with care as it can lead to performance penalties. + If [param length] is set to [code]0[/code], the note will play continuously, according to the voice configuration. It can die off naturally, but it can also continue playing until the [method note_off] method is called explicitly. [param length], [param delay], and [param quantize] must be in 1/16th of a beat. + Use [param disposable] to control whether the created track can be freed after being used. Non-disposable tracks must be treated with care as it can lead to performance penalties. [b]Note:[/b] Streaming must be initialized before this method is called (see [method play]). + + + + + + + + + + + + + Executes a note on event with pitch bending applied using the given voice. Returns the affected, or newly created, track (see also [method SiMMLTrack.get_track_id]). + The resulting pitch is bent from [param note] to [param note_to] over the course of [param bend_length], which must be in 1/16th of a beat. This length cannot be more than the length of the note and will be truncated to fit. This does not affect continuous notes that have the length of [code]0[/code]. + See also [method note_on]. + + @@ -218,21 +235,6 @@ [/codeblocks] - - - - - - - - - - Plays the sound for the given sample number. Returns the affected, or newly created, track (see also [method SiMMLTrack.get_track_id]). - [param length], [param delay], and [param quantize] must be in 1/16th of a beat. - Use [param disposable] to control whether the track can be freed after being used. Making a track non-disposable must be treated with care as it can lead to performance penalties. - [b]Note:[/b] Streaming must be initialized before this method is called (see [method play]). - - @@ -274,6 +276,21 @@ Resumes streaming previously paused by [method pause]. + + + + + + + + + + Plays the sound for the given sample number. Returns the affected, or newly created, track (see also [method SiMMLTrack.get_track_id]). + [param length], [param delay], and [param quantize] must be in 1/16th of a beat. + Use [param disposable] to control whether the track can be freed after being used. Making a track non-disposable must be treated with care as it can lead to performance penalties. + [b]Note:[/b] Streaming must be initialized before this method is called (see [method play]). + + @@ -298,7 +315,7 @@ Executes a sequence, or sequences, provided by the given data and using the given voice. Returns affected, or newly created, tracks (see also [method SiMMLTrack.get_track_id]). [param length], [param delay], and [param quantize] must be in 1/16th of a beat. - Use [param disposable] to control whether the track can be freed after being used. Making a track non-disposable must be treated with care as it can lead to performance penalties. + Use [param disposable] to control whether the created track can be freed after being used. Non-disposable tracks must be treated with care as it can lead to performance penalties. [b]Note:[/b] Streaming must be initialized before this method is called (see [method play]). diff --git a/example/gui/components/PianoRoll.gd b/example/gui/components/PianoRoll.gd index 58ac664..62db386 100644 --- a/example/gui/components/PianoRoll.gd +++ b/example/gui/components/PianoRoll.gd @@ -33,7 +33,7 @@ func _init() -> void: func _ready() -> void: _update_instrument() _update_positions() - + if not Engine.is_editor_hint(): Controller.music_player.instrument_changed.connect(_update_instrument) @@ -85,7 +85,7 @@ func _update_keys() -> void: if is_drumkit: var instrument := Controller.music_player.get_active_instrument() var active_drumkit := Controller.voice_manager.get_drumkit(instrument.type) - + _keys.resize(active_drumkit.size) for i in active_drumkit.size: _keys[i] = i @@ -118,7 +118,7 @@ func _update_keys() -> void: func _update_instrument() -> void: if Engine.is_editor_hint(): return - + var instrument := Controller.music_player.get_active_instrument() is_drumkit = instrument.type != 0 @@ -135,7 +135,7 @@ func _update_positions() -> void: var active_drumkit: Drumkit = null var normal_key_count := 0 - + if is_drumkit: var instrument := Controller.music_player.get_active_instrument() active_drumkit = Controller.voice_manager.get_drumkit(instrument.type) @@ -227,7 +227,7 @@ func _update_active_key(key: int) -> void: var note_length := NOTE_LENGTH if is_drumkit: note_length = NOTE_LENGTH * 2 - + Controller.music_player.play_note(_active_key, note_length) queue_redraw() diff --git a/src/sequencer/base/mml_executor.cpp b/src/sequencer/base/mml_executor.cpp index 015e072..259480d 100644 --- a/src/sequencer/base/mml_executor.cpp +++ b/src/sequencer/base/mml_executor.cpp @@ -63,32 +63,28 @@ void MMLExecutor::execute_single_note(int p_note, int p_tick_length) { _current_tick_count = 0; } -bool MMLExecutor::pitch_bend_from(int p_note, int p_tick_length) { +void MMLExecutor::bend_single_note(int p_to_note, int p_tick_length) { if (_pointer != _note_event || p_tick_length == 0) { - return false; + return; } int tick_length = p_tick_length; - if (_note_event->get_length() != 0) { - // SUS: This check and following adjustments seem inconsistent to me. - // If the note event has length, but the bend event is shorter than that, we - // make the bend event almost as long as the note event, and make the note - // event one tick long. - // However, if the bend event is longer than the note event, then we keep it - // as is, and make the note event shorter by the bend event's length, meaning - // that the note event now has negative length. - if (tick_length < _note_event->get_length()) { + if (_note_event->get_length() > 0) { + // Bending cannot last longer than the note. But the trailing note + // must play for at least 1 tick. + if (tick_length > _note_event->get_length()) { tick_length = _note_event->get_length() - 1; } _note_event->set_length(_note_event->get_length() - tick_length); } _bend_from_event->set_length(0); - _bend_from_event->set_data(p_note); + _bend_from_event->set_data(_note_event->get_data()); _bend_event->set_length(tick_length); + _note_event->set_data(p_to_note); _pointer = _bend_from_event; - return true; + return; } MMLEvent *MMLExecutor::publish_processing_event(MMLEvent *p_event) { diff --git a/src/sequencer/base/mml_executor.h b/src/sequencer/base/mml_executor.h index bc89523..5e8206f 100644 --- a/src/sequencer/base/mml_executor.h +++ b/src/sequencer/base/mml_executor.h @@ -58,7 +58,7 @@ class MMLExecutor { void reset_pointer(); void stop(); void execute_single_note(int p_note, int p_tick_length); - bool pitch_bend_from(int p_note, int p_tick_length); + void bend_single_note(int p_to_note, int p_tick_length); MMLEvent *publish_processing_event(MMLEvent *p_event); diff --git a/src/sequencer/simml_track.cpp b/src/sequencer/simml_track.cpp index a223aef..0dde453 100644 --- a/src/sequencer/simml_track.cpp +++ b/src/sequencer/simml_track.cpp @@ -123,10 +123,6 @@ void SiMMLTrack::set_pitch_bend(int p_value) { _channel->set_pitch(_pitch_index + _pitch_bend); } -void SiMMLTrack::start_pitch_bending(int p_note_from, int p_tick_length) { - _executor->pitch_bend_from(p_note_from, p_tick_length); -} - void SiMMLTrack::set_note_immediately(int p_note, int p_sample_length, bool p_slur) { // Play with key off when quantize_ratio == 0 or p_sample_length != 0. if (!p_slur && (_quantize_ratio == 0 || p_sample_length > 0)) { @@ -852,6 +848,10 @@ void SiMMLTrack::key_off(int p_sample_delay, bool p_with_reset) { } } +void SiMMLTrack::bend_note(int p_to_note, int p_tick_length) { + _executor->bend_single_note(p_to_note, p_tick_length); +} + void SiMMLTrack::sequence_on(const Ref &p_data, MMLSequence *p_sequence, int p_sample_length, int p_sample_delay) { ERR_FAIL_NULL(p_sequence); diff --git a/src/sequencer/simml_track.h b/src/sequencer/simml_track.h index 19a7a7a..5fa5b45 100644 --- a/src/sequencer/simml_track.h +++ b/src/sequencer/simml_track.h @@ -277,7 +277,6 @@ class SiMMLTrack : public Object { int get_pitch_bend() const { return _pitch_bend; } void set_pitch_bend(int p_value); - void start_pitch_bending(int p_note_from, int p_tick_length); int get_pitch_shift() const { return _pitch_shift; } void set_pitch_shift(int p_value) { _pitch_shift = p_value; } @@ -374,6 +373,7 @@ class SiMMLTrack : public Object { void key_on(int p_note, int p_tick_length = 0, int p_sample_delay = 0); void key_off(int p_sample_delay = 0, bool p_with_reset = false); + void bend_note(int p_to_note, int p_tick_length); void sequence_on(const Ref &p_data, MMLSequence *p_sequence, int p_sample_length = 0, int p_sample_delay = 0); void sequence_off(int p_sample_delay = 0, bool p_with_reset = false); diff --git a/src/sion_driver.cpp b/src/sion_driver.cpp index 218f89c..9153608 100644 --- a/src/sion_driver.cpp +++ b/src/sion_driver.cpp @@ -664,10 +664,8 @@ void SiONDriver::resume() { _is_paused = false; } -SiMMLTrack *SiONDriver::play_sound(int p_sample_number, double p_length, double p_delay, double p_quant, int p_track_id, bool p_disposable) { - ERR_FAIL_COND_V_MSG(!_is_streaming, nullptr, "SiONDriver: Driver is not streaming, you must call SiONDriver.play() first."); - ERR_FAIL_COND_V_MSG(p_length < 0, nullptr, "SiONDriver: Sound length cannot be less than zero."); - ERR_FAIL_COND_V_MSG(p_delay < 0, nullptr, "SiONDriver: Sound delay cannot be less than zero."); +SiMMLTrack *SiONDriver::_find_or_create_track(int p_track_id, double p_delay, double p_quant, bool p_disposable, int *r_delay_samples) { + ERR_FAIL_COND_V_MSG(p_delay < 0, nullptr, "SiONDriver: Playback delay cannot be less than zero."); int internal_track_id = (p_track_id & SiMMLTrack::TRACK_ID_FILTER) | SiMMLTrack::DRIVER_NOTE; double delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant); @@ -691,55 +689,67 @@ SiMMLTrack *SiONDriver::play_sound(int p_sample_number, double p_length, double } } - if (!track) { - track = sequencer->create_controllable_track(internal_track_id, p_disposable); - } + *r_delay_samples = delay_samples; if (track) { - track->set_channel_module_type(SiONModuleType::MODULE_SAMPLE, 0); - track->key_on(p_sample_number, _convert_event_length(p_length), delay_samples); + return track; + } + + track = sequencer->create_controllable_track(internal_track_id, p_disposable); + ERR_FAIL_NULL_V_MSG(track, nullptr, "SiONDriver: Failed to allocate a track for playback. Pushing the limits?"); + return track; +} + +SiMMLTrack *SiONDriver::sample_on(int p_sample_number, double p_length, double p_delay, double p_quant, int p_track_id, bool p_disposable) { + ERR_FAIL_COND_V_MSG(!_is_streaming, nullptr, "SiONDriver: Driver is not streaming, you must call SiONDriver.play() first."); + ERR_FAIL_COND_V_MSG(p_length < 0, nullptr, "SiONDriver: Sample length cannot be less than zero."); + + int delay_samples = 0; + SiMMLTrack *track = _find_or_create_track(p_delay, p_quant, p_track_id, p_disposable, &delay_samples); + if (!track) { + return nullptr; } + track->set_channel_module_type(SiONModuleType::MODULE_SAMPLE, 0); + track->key_on(p_sample_number, _convert_event_length(p_length), delay_samples); + return track; } SiMMLTrack *SiONDriver::note_on(int p_note, const Ref &p_voice, double p_length, double p_delay, double p_quant, int p_track_id, bool p_disposable) { ERR_FAIL_COND_V_MSG(!_is_streaming, nullptr, "SiONDriver: Driver is not streaming, you must call SiONDriver.play() first."); ERR_FAIL_COND_V_MSG(p_length < 0, nullptr, "SiONDriver: Note length cannot be less than zero."); - ERR_FAIL_COND_V_MSG(p_delay < 0, nullptr, "SiONDriver: Note delay cannot be less than zero."); - int internal_track_id = (p_track_id & SiMMLTrack::TRACK_ID_FILTER) | SiMMLTrack::DRIVER_NOTE; - double delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant); + int delay_samples = 0; + SiMMLTrack *track = _find_or_create_track(p_delay, p_quant, p_track_id, p_disposable, &delay_samples); + if (!track) { + return nullptr; + } - SiMMLTrack *track = nullptr; + if (p_voice.is_valid()) { + p_voice->update_track_voice(track); + } + track->key_on(p_note, _convert_event_length(p_length), delay_samples); - // Check track ID conflicts. - if (_note_on_exception_mode != NEM_IGNORE) { - // Find a track with the same sound timings. - track = sequencer->find_active_track(internal_track_id, delay_samples); + return track; +} - if (track && _note_on_exception_mode == NEM_REJECT) { - return nullptr; - } - if (track && _note_on_exception_mode == NEM_SHIFT) { - int step = sequencer->calculate_sample_length(p_quant); - while (track) { - delay_samples += step; - track = sequencer->find_active_track(internal_track_id, delay_samples); - } - } - } +SiMMLTrack *SiONDriver::note_on_with_bend(int p_note, int p_note_to, double p_bend_length, const Ref &p_voice, double p_length, double p_delay, double p_quant, int p_track_id, bool p_disposable) { + ERR_FAIL_COND_V_MSG(!_is_streaming, nullptr, "SiONDriver: Driver is not streaming, you must call SiONDriver.play() first."); + ERR_FAIL_COND_V_MSG(p_length < 0, nullptr, "SiONDriver: Note length cannot be less than zero."); + ERR_FAIL_COND_V_MSG(p_bend_length < 0, nullptr, "SiONDriver: Pitch bending length cannot be less than zero."); + int delay_samples = 0; + SiMMLTrack *track = _find_or_create_track(p_delay, p_quant, p_track_id, p_disposable, &delay_samples); if (!track) { - track = sequencer->create_controllable_track(internal_track_id, p_disposable); + return nullptr; } - if (track) { - if (p_voice.is_valid()) { - p_voice->update_track_voice(track); - } - track->key_on(p_note, _convert_event_length(p_length), delay_samples); + if (p_voice.is_valid()) { + p_voice->update_track_voice(track); } + track->key_on(p_note, _convert_event_length(p_length), delay_samples); + track->bend_note(p_note_to, _convert_event_length(p_bend_length)); return track; } @@ -749,7 +759,7 @@ TypedArray SiONDriver::note_off(int p_note, int p_track_id, double p ERR_FAIL_COND_V_MSG(p_delay < 0, TypedArray(), "SiONDriver: Note off delay cannot be less than zero."); int internal_track_id = (p_track_id & SiMMLTrack::TRACK_ID_FILTER) | SiMMLTrack::DRIVER_NOTE; - double delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant); + int delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant); TypedArray tracks; for (SiMMLTrack *track : sequencer->get_tracks()) { @@ -776,7 +786,7 @@ TypedArray SiONDriver::sequence_on(const Ref &p_data, cons ERR_FAIL_COND_V_MSG(p_delay < 0, TypedArray(), "SiONDriver: Sequence delay cannot be less than zero."); int internal_track_id = (p_track_id & SiMMLTrack::TRACK_ID_FILTER) | SiMMLTrack::DRIVER_SEQUENCE; - double delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant); + int delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant); int length_samples = sequencer->calculate_sample_length(p_length); TypedArray tracks; @@ -785,10 +795,13 @@ TypedArray SiONDriver::sequence_on(const Ref &p_data, cons while (sequence) { if (sequence->is_active()) { SiMMLTrack *track = sequencer->create_controllable_track(internal_track_id, p_disposable); + ERR_FAIL_NULL_V_MSG(track, tracks, "SiONDriver: Failed to allocate a track for playback. Pushing the limits?"); + track->sequence_on(p_data, sequence, length_samples, delay_samples); if (p_voice.is_valid()) { p_voice->update_track_voice(track); } + tracks.push_back(track); } @@ -802,7 +815,7 @@ TypedArray SiONDriver::sequence_off(int p_track_id, double p_delay, ERR_FAIL_COND_V_MSG(p_delay < 0, TypedArray(), "SiONDriver: Sequence off delay cannot be less than zero."); int internal_track_id = (p_track_id & SiMMLTrack::TRACK_ID_FILTER) | SiMMLTrack::DRIVER_SEQUENCE; - double delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant); + int delay_samples = sequencer->calculate_sample_delay(0, p_delay, p_quant); TypedArray tracks; for (SiMMLTrack *track : sequencer->get_tracks()) { @@ -1320,9 +1333,11 @@ void SiONDriver::_bind_methods() { ClassDB::bind_method(D_METHOD("pause"), &SiONDriver::pause); ClassDB::bind_method(D_METHOD("resume"), &SiONDriver::resume); - ClassDB::bind_method(D_METHOD("play_sound", "sample_number", "length", "delay", "quantize", "track_id", "disposable"), &SiONDriver::play_sound, DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("sample_on", "sample_number", "length", "delay", "quantize", "track_id", "disposable"), &SiONDriver::sample_on, DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(true)); ClassDB::bind_method(D_METHOD("note_on", "note", "voice", "length", "delay", "quantize", "track_id", "disposable"), &SiONDriver::note_on, DEFVAL((Object *)nullptr), DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("note_on_with_bend", "note", "note_to", "bend_length", "voice", "length", "delay", "quantize", "track_id", "disposable"), &SiONDriver::note_on_with_bend, DEFVAL((Object *)nullptr), DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(true)); ClassDB::bind_method(D_METHOD("note_off", "note", "track_id", "delay", "quantize", "stop_immediately"), &SiONDriver::note_off, DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("sequence_on", "data", "voice", "length", "delay", "quantize", "track_id", "disposable"), &SiONDriver::sequence_on, DEFVAL((Object *)nullptr), DEFVAL(0), DEFVAL(0), DEFVAL(1), DEFVAL(0), DEFVAL(true)); ClassDB::bind_method(D_METHOD("sequence_off", "track_id", "delay", "quantize", "stop_with_reset"), &SiONDriver::sequence_off, DEFVAL(0), DEFVAL(1), DEFVAL(false)); diff --git a/src/sion_driver.h b/src/sion_driver.h index d8ff8af..dc26b5d 100644 --- a/src/sion_driver.h +++ b/src/sion_driver.h @@ -163,6 +163,8 @@ class SiONDriver : public Node { // Send the CHANGE_BPM event when position changes. bool _notify_change_bpm_on_position_changed = true; + SiMMLTrack *_find_or_create_track(int p_track_id, double p_delay, double p_quant, bool p_disposable, int *r_delay_samples); + void _update_volume(); void _fade_callback(double p_value); @@ -384,9 +386,11 @@ class SiONDriver : public Node { void pause(); void resume(); - SiMMLTrack *play_sound(int p_sample_number, double p_length = 0, double p_delay = 0, double p_quant = 0, int p_track_id = 0, bool p_disposable = true); + SiMMLTrack *sample_on(int p_sample_number, double p_length = 0, double p_delay = 0, double p_quant = 0, int p_track_id = 0, bool p_disposable = true); SiMMLTrack *note_on(int p_note, const Ref &p_voice = Ref(), double p_length = 0, double p_delay = 0, double p_quant = 0, int p_track_id = 0, bool p_disposable = true); + SiMMLTrack *note_on_with_bend(int p_note, int p_note_to, double p_bend_length, const Ref &p_voice = Ref(), double p_length = 0, double p_delay = 0, double p_quant = 0, int p_track_id = 0, bool p_disposable = true); TypedArray note_off(int p_note, int p_track_id = 0, double p_delay = 0, double p_quant = 0, bool p_stop_immediately = false); + TypedArray sequence_on(const Ref &p_data, const Ref &p_voice = Ref(), double p_length = 0, double p_delay = 0, double p_quant = 1, int p_track_id = 0, bool p_disposable = true); TypedArray sequence_off(int p_track_id, double p_delay = 0, double p_quant = 1, bool p_stop_with_reset = false);