Skip to content

Commit

Permalink
Fix immediate mode pitch bending implementation and expose a method t…
Browse files Browse the repository at this point in the history
…o use it

Also contains a breaking rename:
- SiONDriver.play_sound -> sample_on.

This makes it consistent with other related methods
while removing ambiguity between it and play().
  • Loading branch information
YuriSizov committed Nov 12, 2024
1 parent 72e3b98 commit f450900
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 81 deletions.
53 changes: 35 additions & 18 deletions doc_classes/SiONDriver.xml
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,28 @@
<param index="6" name="disposable" type="bool" default="true" />
<description>
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]).
</description>
</method>
<method name="note_on_with_bend">
<return type="SiMMLTrack" />
<param index="0" name="note" type="int" />
<param index="1" name="note_to" type="int" />
<param index="2" name="bend_length" type="float" />
<param index="3" name="voice" type="SiONVoice" default="null" />
<param index="4" name="length" type="float" default="0" />
<param index="5" name="delay" type="float" default="0" />
<param index="6" name="quantize" type="float" default="0" />
<param index="7" name="track_id" type="int" default="0" />
<param index="8" name="disposable" type="bool" default="true" />
<description>
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].
</description>
</method>
<method name="pause">
<return type="void" />
<description>
Expand All @@ -218,21 +235,6 @@
[/codeblocks]
</description>
</method>
<method name="play_sound">
<return type="SiMMLTrack" />
<param index="0" name="sample_number" type="int" />
<param index="1" name="length" type="float" default="0" />
<param index="2" name="delay" type="float" default="0" />
<param index="3" name="quantize" type="float" default="0" />
<param index="4" name="track_id" type="int" default="0" />
<param index="5" name="disposable" type="bool" default="true" />
<description>
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]).
</description>
</method>
<method name="queue_compile">
<return type="int" />
<param index="0" name="mml" type="String" />
Expand Down Expand Up @@ -274,6 +276,21 @@
Resumes streaming previously paused by [method pause].
</description>
</method>
<method name="sample_on">
<return type="SiMMLTrack" />
<param index="0" name="sample_number" type="int" />
<param index="1" name="length" type="float" default="0" />
<param index="2" name="delay" type="float" default="0" />
<param index="3" name="quantize" type="float" default="0" />
<param index="4" name="track_id" type="int" default="0" />
<param index="5" name="disposable" type="bool" default="true" />
<description>
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]).
</description>
</method>
<method name="sequence_off">
<return type="SiMMLTrack[]" />
<param index="0" name="track_id" type="int" />
Expand All @@ -298,7 +315,7 @@
<description>
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]).
</description>
</method>
Expand Down
10 changes: 5 additions & 5 deletions example/gui/components/PianoRoll.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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)
Expand Down Expand Up @@ -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()

Expand Down
22 changes: 9 additions & 13 deletions src/sequencer/base/mml_executor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/sequencer/base/mml_executor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
8 changes: 4 additions & 4 deletions src/sequencer/simml_track.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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<SiMMLData> &p_data, MMLSequence *p_sequence, int p_sample_length, int p_sample_delay) {
ERR_FAIL_NULL(p_sequence);

Expand Down
2 changes: 1 addition & 1 deletion src/sequencer/simml_track.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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<SiMMLData> &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);
Expand Down
91 changes: 53 additions & 38 deletions src/sion_driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<SiONVoice> &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<SiONVoice> &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;
}
Expand All @@ -749,7 +759,7 @@ TypedArray<SiMMLTrack> SiONDriver::note_off(int p_note, int p_track_id, double p
ERR_FAIL_COND_V_MSG(p_delay < 0, TypedArray<SiMMLTrack>(), "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<SiMMLTrack> tracks;
for (SiMMLTrack *track : sequencer->get_tracks()) {
Expand All @@ -776,7 +786,7 @@ TypedArray<SiMMLTrack> SiONDriver::sequence_on(const Ref<SiONData> &p_data, cons
ERR_FAIL_COND_V_MSG(p_delay < 0, TypedArray<SiMMLTrack>(), "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<SiMMLTrack> tracks;
Expand All @@ -785,10 +795,13 @@ TypedArray<SiMMLTrack> SiONDriver::sequence_on(const Ref<SiONData> &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);
}

Expand All @@ -802,7 +815,7 @@ TypedArray<SiMMLTrack> SiONDriver::sequence_off(int p_track_id, double p_delay,
ERR_FAIL_COND_V_MSG(p_delay < 0, TypedArray<SiMMLTrack>(), "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<SiMMLTrack> tracks;
for (SiMMLTrack *track : sequencer->get_tracks()) {
Expand Down Expand Up @@ -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));

Expand Down
Loading

0 comments on commit f450900

Please sign in to comment.