synthio: Add Synthesizer.note_state

This enables the specific use case of checking whether a note's release
phase has ended, but is also potentially useful to implement a sort of
"voice stealing" algorithm in Python code, which can take account of
the note's envelope state as well as other factors specific to the
program.
This commit is contained in:
Jeff Epler 2023-07-20 13:16:00 -05:00
parent 305303996d
commit 346f08f8b9
No known key found for this signature in database
GPG Key ID: D5BF15AB975AB4DE
8 changed files with 98 additions and 6 deletions

View File

@ -31,6 +31,7 @@
#include "py/binary.h"
#include "py/objproperty.h"
#include "py/runtime.h"
#include "py/enum.h"
#include "shared-bindings/util.h"
#include "shared-bindings/synthio/Biquad.h"
#include "shared-bindings/synthio/Synthesizer.h"
@ -256,7 +257,9 @@ MP_PROPERTY_GETTER(synthio_synthesizer_sample_rate_obj,
(mp_obj_t)&synthio_synthesizer_get_sample_rate_obj);
//| pressed: NoteSequence
//| """A sequence of the currently pressed notes (read-only property)"""
//| """A sequence of the currently pressed notes (read-only property).
//|
//| This does not include notes in the release phase of the envelope."""
//|
STATIC mp_obj_t synthio_synthesizer_obj_get_pressed(mp_obj_t self_in) {
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
@ -268,6 +271,23 @@ MP_DEFINE_CONST_FUN_OBJ_1(synthio_synthesizer_get_pressed_obj, synthio_synthesiz
MP_PROPERTY_GETTER(synthio_synthesizer_pressed_obj,
(mp_obj_t)&synthio_synthesizer_get_pressed_obj);
//| def note_info(note: Note) -> Tuple[Optional[EnvelopeState], float]:
//| """Get info about a note's current envelope state
//|
//| If the note is currently playing (including in the release phase), the returned value gives the current envelope state and the current envelope value.
//|
//| If the note is not playing on this synthesizer, returns the tuple ``(None, 0.0)``."""
STATIC mp_obj_t synthio_synthesizer_obj_note_info(mp_obj_t self_in, mp_obj_t note) {
synthio_synthesizer_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self);
mp_float_t vol = MICROPY_FLOAT_CONST(0.0);
envelope_state_e state = common_hal_synthio_synthesizer_note_info(self, note, &vol);
return MP_OBJ_NEW_TUPLE(
cp_enum_find(&synthio_note_state_type, state),
mp_obj_new_float(vol));
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_synthesizer_note_info_obj, synthio_synthesizer_obj_note_info);
//| blocks: List[BlockInput]
//| """A list of blocks to advance whether or not they are associated with a playing note.
//|
@ -417,6 +437,7 @@ STATIC const mp_rom_map_elem_t synthio_synthesizer_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&synthio_synthesizer_sample_rate_obj) },
{ MP_ROM_QSTR(MP_QSTR_max_polyphony), MP_ROM_INT(CIRCUITPY_SYNTHIO_MAX_CHANNELS) },
{ MP_ROM_QSTR(MP_QSTR_pressed), MP_ROM_PTR(&synthio_synthesizer_pressed_obj) },
{ MP_ROM_QSTR(MP_QSTR_note_info), MP_ROM_PTR(&synthio_synthesizer_note_info_obj) },
{ MP_ROM_QSTR(MP_QSTR_blocks), MP_ROM_PTR(&synthio_synthesizer_blocks_obj) },
};
STATIC MP_DEFINE_CONST_DICT(synthio_synthesizer_locals_dict, synthio_synthesizer_locals_dict_table);

View File

@ -45,3 +45,4 @@ void common_hal_synthio_synthesizer_retrigger(synthio_synthesizer_obj_t *self, m
void common_hal_synthio_synthesizer_release_all(synthio_synthesizer_obj_t *self);
mp_obj_t common_hal_synthio_synthesizer_get_pressed_notes(synthio_synthesizer_obj_t *self);
mp_obj_t common_hal_synthio_synthesizer_get_blocks(synthio_synthesizer_obj_t *self);
envelope_state_e common_hal_synthio_synthesizer_note_info(synthio_synthesizer_obj_t *self, mp_obj_t note, mp_float_t *vol_out);

View File

@ -45,6 +45,22 @@
#include "shared-module/synthio/LFO.h"
MAKE_ENUM_VALUE(synthio_note_state_type, note_state, ATTACK, SYNTHIO_ENVELOPE_STATE_ATTACK);
MAKE_ENUM_VALUE(synthio_note_state_type, note_state, DECAY, SYNTHIO_ENVELOPE_STATE_DECAY);
MAKE_ENUM_VALUE(synthio_note_state_type, note_state, SUSTAIN, SYNTHIO_ENVELOPE_STATE_SUSTAIN);
MAKE_ENUM_VALUE(synthio_note_state_type, note_state, RELEASE, SYNTHIO_ENVELOPE_STATE_RELEASE);
MAKE_ENUM_MAP(synthio_note_state) {
MAKE_ENUM_MAP_ENTRY(note_state, ATTACK),
MAKE_ENUM_MAP_ENTRY(note_state, DECAY),
MAKE_ENUM_MAP_ENTRY(note_state, SUSTAIN),
MAKE_ENUM_MAP_ENTRY(note_state, RELEASE),
};
STATIC MP_DEFINE_CONST_DICT(synthio_note_state_locals_dict, synthio_note_state_locals_table);
MAKE_PRINTER(synthio, synthio_note_state);
MAKE_ENUM_TYPE(synthio, NoteState, synthio_note_state);
#define default_attack_time (MICROPY_FLOAT_CONST(0.1))
#define default_decay_time (MICROPY_FLOAT_CONST(0.05))
#define default_release_time (MICROPY_FLOAT_CONST(0.2))
@ -316,6 +332,7 @@ STATIC const mp_rom_map_elem_t synthio_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_MathOperation), MP_ROM_PTR(&synthio_math_operation_type) },
{ MP_ROM_QSTR(MP_QSTR_MidiTrack), MP_ROM_PTR(&synthio_miditrack_type) },
{ MP_ROM_QSTR(MP_QSTR_Note), MP_ROM_PTR(&synthio_note_type) },
{ MP_ROM_QSTR(MP_QSTR_NoteState), MP_ROM_PTR(&synthio_note_state_type) },
{ MP_ROM_QSTR(MP_QSTR_LFO), MP_ROM_PTR(&synthio_lfo_type) },
{ MP_ROM_QSTR(MP_QSTR_Synthesizer), MP_ROM_PTR(&synthio_synthesizer_type) },
{ MP_ROM_QSTR(MP_QSTR_from_file), MP_ROM_PTR(&synthio_from_file_obj) },

View File

@ -29,10 +29,16 @@
#include "py/objnamedtuple.h"
#include "py/enum.h"
typedef enum {
SYNTHIO_ENVELOPE_STATE_ATTACK, SYNTHIO_ENVELOPE_STATE_DECAY,
SYNTHIO_ENVELOPE_STATE_SUSTAIN, SYNTHIO_ENVELOPE_STATE_RELEASE
} envelope_state_e;
typedef enum synthio_bend_mode_e {
SYNTHIO_BEND_MODE_STATIC, SYNTHIO_BEND_MODE_VIBRATO, SYNTHIO_BEND_MODE_SWEEP, SYNTHIO_BEND_MODE_SWEEP_IN
} synthio_bend_mode_t;
extern const mp_obj_type_t synthio_note_state_type;
extern const cp_enum_obj_t bend_mode_VIBRATO_obj;
extern const mp_obj_type_t synthio_bend_mode_type;
typedef struct synthio_synth synthio_synth_t;

View File

@ -185,6 +185,17 @@ mp_obj_t common_hal_synthio_synthesizer_get_pressed_notes(synthio_synthesizer_ob
return MP_OBJ_FROM_PTR(result);
}
envelope_state_e common_hal_synthio_synthesizer_note_info(synthio_synthesizer_obj_t *self, mp_obj_t note, mp_float_t *vol_out) {
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
if (self->synth.span.note_obj[chan] == note) {
*vol_out = self->synth.envelope_state[chan].level / 32767.;
return self->synth.envelope_state[chan].state;
}
}
return (envelope_state_e) - 1;
}
mp_obj_t common_hal_synthio_synthesizer_get_blocks(synthio_synthesizer_obj_t *self) {
return self->blocks;
}

View File

@ -35,6 +35,7 @@
#define SYNTHIO_FREQUENCY_SHIFT (16)
#include "shared-module/audiocore/__init__.h"
#include "shared-bindings/synthio/__init__.h"
typedef struct {
uint16_t dur;
@ -49,11 +50,6 @@ typedef struct {
uint16_t attack_level, sustain_level;
} synthio_envelope_definition_t;
typedef enum {
SYNTHIO_ENVELOPE_STATE_ATTACK, SYNTHIO_ENVELOPE_STATE_DECAY,
SYNTHIO_ENVELOPE_STATE_SUSTAIN, SYNTHIO_ENVELOPE_STATE_RELEASE
} envelope_state_e;
typedef struct {
int16_t level;
uint16_t substep;

View File

@ -0,0 +1,17 @@
from synthio import Synthesizer, Note, Envelope
from audiocore import get_buffer
s = Synthesizer()
n = Note(440, envelope=Envelope())
print("{} {:.2f}".format(*s.note_info(n)))
s.press(n)
print("press")
for _ in range(9):
print("{} {:.2f}".format(*s.note_info(n)))
get_buffer(s)
s.release(n)
print("release")
for _ in range(11):
print("{} {:.2f}".format(*s.note_info(n)))
get_buffer(s)

View File

@ -0,0 +1,23 @@
None 0.00
press
synthio.NoteState.ATTACK 0.23
synthio.NoteState.ATTACK 0.46
synthio.NoteState.ATTACK 0.70
synthio.NoteState.ATTACK 0.93
synthio.NoteState.DECAY 1.00
synthio.NoteState.DECAY 0.91
synthio.NoteState.DECAY 0.81
synthio.NoteState.SUSTAIN 0.80
synthio.NoteState.SUSTAIN 0.80
release
synthio.NoteState.RELEASE 0.80
synthio.NoteState.RELEASE 0.71
synthio.NoteState.RELEASE 0.61
synthio.NoteState.RELEASE 0.52
synthio.NoteState.RELEASE 0.43
synthio.NoteState.RELEASE 0.34
synthio.NoteState.RELEASE 0.24
synthio.NoteState.RELEASE 0.15
synthio.NoteState.RELEASE 0.06
synthio.NoteState.RELEASE 0.00
None 0.00