From 3a18c1d1f79ac24573b030288c48af2f458c87f1 Mon Sep 17 00:00:00 2001 From: Kinga Janicka <50145113+KingaJanicka@users.noreply.github.com> Date: Wed, 6 Dec 2023 12:03:48 +0000 Subject: [PATCH] Adds stub type definitions to surgepy. Fixes #7362 (#7363) * Adds stub type definitions to surgepy. Fixes #7362 * Update surgepy.cpp Added comment explaining how to generate stub types. * Clang Format --------- Co-authored-by: Paul Walker --- AUTHORS | 1 + src/surge-python/setup.py | 6 +- src/surge-python/surgepy.cpp | 7 +- src/surge-python/surgepy/__init__.pyi | 14 + .../surgepy/_surgepy/__init__.pyi | 273 ++++++++++++++++++ .../surgepy/_surgepy/constants.pyi | 117 ++++++++ 6 files changed, 416 insertions(+), 2 deletions(-) create mode 100644 src/surge-python/surgepy/__init__.pyi create mode 100644 src/surge-python/surgepy/_surgepy/__init__.pyi create mode 100644 src/surge-python/surgepy/_surgepy/constants.pyi diff --git a/AUTHORS b/AUTHORS index 8c95afd5a32..bf489285545 100644 --- a/AUTHORS +++ b/AUTHORS @@ -53,3 +53,4 @@ David Carlier Xenakios Alexandr Zyurkalov Morgan Holly +Kinga Janicka \ No newline at end of file diff --git a/src/surge-python/setup.py b/src/surge-python/setup.py index da9c0197752..6684e9ab0c6 100644 --- a/src/surge-python/setup.py +++ b/src/surge-python/setup.py @@ -11,11 +11,14 @@ with CMake. For more information see https://scikit-build.readthedocs.io """ from skbuild import setup - +from pathlib import Path def just_surgepy(cmake_manifest): return [x for x in cmake_manifest if "surgepy" in x] +def find_stubs(path: Path): + return [str(pyi.relative_to(path)) for pyi in path.rglob("*.pyi")] + setup( name="surgepy", @@ -35,4 +38,5 @@ def just_surgepy(cmake_manifest): "-DSURGE_SKIP_STANDALONE=TRUE", ], cmake_process_manifest_hook=just_surgepy, + package_data={"surgepy": [*find_stubs(path=Path("surgepy"))]}, ) diff --git a/src/surge-python/surgepy.cpp b/src/surge-python/surgepy.cpp index feaac0eb714..5ff1a42b448 100644 --- a/src/surge-python/surgepy.cpp +++ b/src/surge-python/surgepy.cpp @@ -33,6 +33,11 @@ namespace py = pybind11; +/* + * Generate stub types by running `pip install pybind11-stubgen && pybind11-stubgen surgepy` + * and copy `out/` folder contents to `./surgepy`. + */ + class PythonPluginLayerProxy : public SurgeSynthesizer::PluginLayer { public: @@ -1266,4 +1271,4 @@ PYBIND11_MODULE(surgepy, m) py::enum_(m, "TuningApplicationMode") .value("RETUNE_ALL", SurgeStorage::TuningApplicationMode::RETUNE_ALL) .value("RETUNE_MIDI_ONLY", SurgeStorage::TuningApplicationMode::RETUNE_MIDI_ONLY); -} \ No newline at end of file +} diff --git a/src/surge-python/surgepy/__init__.pyi b/src/surge-python/surgepy/__init__.pyi new file mode 100644 index 00000000000..0b0810dde9d --- /dev/null +++ b/src/surge-python/surgepy/__init__.pyi @@ -0,0 +1,14 @@ +from __future__ import annotations +from surgepy._surgepy import SurgeControlGroup +from surgepy._surgepy import SurgeControlGroupEntry +from surgepy._surgepy import SurgeModRouting +from surgepy._surgepy import SurgeModSource +from surgepy._surgepy import SurgeNamedParamId +from surgepy._surgepy import SurgeSynthesizer +from surgepy._surgepy import SurgeSynthesizer_ID +from surgepy._surgepy import TuningApplicationMode +from surgepy._surgepy import constants +from surgepy._surgepy import createSurge +from surgepy._surgepy import getVersion +from . import _surgepy +__all__ = ['SurgeControlGroup', 'SurgeControlGroupEntry', 'SurgeModRouting', 'SurgeModSource', 'SurgeNamedParamId', 'SurgeSynthesizer', 'SurgeSynthesizer_ID', 'TuningApplicationMode', 'constants', 'createSurge', 'getVersion'] diff --git a/src/surge-python/surgepy/_surgepy/__init__.pyi b/src/surge-python/surgepy/_surgepy/__init__.pyi new file mode 100644 index 00000000000..023aa3a83e3 --- /dev/null +++ b/src/surge-python/surgepy/_surgepy/__init__.pyi @@ -0,0 +1,273 @@ +""" +Python bindings for Surge XT Synthesizer +""" +from __future__ import annotations +import numpy +import typing +from . import constants +__all__ = ['SurgeControlGroup', 'SurgeControlGroupEntry', 'SurgeModRouting', 'SurgeModSource', 'SurgeNamedParamId', 'SurgeSynthesizer', 'SurgeSynthesizer_ID', 'TuningApplicationMode', 'constants', 'createSurge', 'getVersion'] +class SurgeControlGroup: + def __repr__(self) -> str: + ... + def getEntries(self) -> list[SurgePyControlGroupEntry]: + ... + def getId(self) -> int: + ... + def getName(self) -> str: + ... +class SurgeControlGroupEntry: + def __repr__(self) -> str: + ... + def getEntry(self) -> int: + ... + def getParams(self) -> list[SurgePyNamedParam]: + ... + def getScene(self) -> int: + ... +class SurgeModRouting: + def __repr__(self) -> str: + ... + def getDepth(self) -> float: + ... + def getDest(self) -> SurgeNamedParamId: + ... + def getNormalizedDepth(self) -> float: + ... + def getSource(self) -> SurgeModSource: + ... + def getSourceIndex(self) -> int: + ... + def getSourceScene(self) -> int: + ... +class SurgeModSource: + def __repr__(self) -> str: + ... + def getModSource(self) -> int: + ... + def getName(self) -> str: + ... +class SurgeNamedParamId: + def __repr__(self) -> str: + ... + def getId(self) -> SurgeSynthesizer_ID: + ... + def getName(self) -> str: + ... +class SurgeSynthesizer: + mpeEnabled: bool + tuningApplicationMode: ... + def __repr__(self) -> str: + ... + def allNotesOff(self) -> None: + """ + Turn off all playing notes + """ + def channelAftertouch(self, channel: int, value: int) -> None: + """ + Send the channel aftertouch MIDI message + """ + def channelController(self, channel: int, cc: int, value: int) -> None: + """ + Set MIDI controller on channel to value + """ + def createMultiBlock(self, blockCapacity: int) -> numpy.ndarray[numpy.float32]: + """ + Create a numpy array suitable to hold up to b blocks of Surge XT processing in processMultiBlock + """ + def createSynthSideId(self, arg0: int) -> SurgeSynthesizer_ID: + ... + def fromSynthSideId(self, arg0: int, arg1: SurgeSynthesizer_ID) -> bool: + ... + def getAllModRoutings(self) -> dict: + """ + Get the entire modulation matrix for this instance. + """ + def getBlockSize(self) -> int: + ... + def getControlGroup(self, entry: int) -> SurgePyControlGroup: + """ + Gather the parameters groups for a surge.constants.cg_ control group + """ + def getFactoryDataPath(self) -> str: + ... + def getModDepth01(self, targetParameter: SurgePyNamedParam, modulationSource: SurgePyModSource, scene: int = 0, index: int = 0) -> float: + """ + Get the modulation depth from a source to a parameter. + """ + def getModSource(self, modId: int) -> SurgePyModSource: + """ + Given a constant from surge.constants.ms_*, provide a modulator object + """ + def getNumInputs(self) -> int: + ... + def getNumOutputs(self) -> int: + ... + def getOutput(self) -> numpy.ndarray[numpy.float32]: + """ + Retrieve the internal output buffer as a 2 * BLOCK_SIZE numpy array. + """ + def getParamDef(self, arg0: SurgePyNamedParam) -> float: + """ + Parameter default value, as a float + """ + def getParamDisplay(self, arg0: SurgePyNamedParam) -> str: + """ + Parameter value display (stringified and formatted) + """ + def getParamInfo(self, arg0: SurgePyNamedParam) -> str: + """ + Parameter value info (formatted) + """ + def getParamMax(self, arg0: SurgePyNamedParam) -> float: + """ + Parameter maximum value, as a float + """ + def getParamMin(self, arg0: SurgePyNamedParam) -> float: + """ + Parameter minimum value, as a float. + """ + def getParamVal(self, arg0: SurgePyNamedParam) -> float: + """ + Parameter current value in this Surge XT instance, as a float + """ + def getParamValType(self, arg0: SurgePyNamedParam) -> str: + """ + Parameter types float, int or bool are supported + """ + def getParameterName(self, arg0: SurgeSynthesizer_ID) -> str: + """ + Given a parameter, return its name as displayed by the synth. + """ + def getPatch(self) -> dict: + """ + Get a Python dictionary with the Surge XT parameters laid out in the logical patch format + """ + def getSampleRate(self) -> float: + ... + def getUserDataPath(self) -> str: + ... + def isActiveModulation(self, targetParameter: SurgePyNamedParam, modulationSource: SurgePyModSource, scene: int = 0, index: int = 0) -> bool: + """ + Is there an established modulation between target and source? + """ + def isBipolarModulation(self, modulationSource: SurgePyModSource) -> bool: + """ + Is the given modulation source bipolar? + """ + def isValidModulation(self, targetParameter: SurgePyNamedParam, modulationSource: SurgePyModSource) -> bool: + """ + Is it possible to modulate between target and source? + """ + def loadKBMFile(self, arg0: str) -> None: + """ + Load a KBM mapping file and apply tuning to this instance + """ + def loadPatch(self, path: str) -> bool: + """ + Load a Surge XT .fxp patch from the file system. + """ + def loadSCLFile(self, arg0: str) -> None: + """ + Load an SCL tuning file and apply tuning to this instance + """ + def pitchBend(self, channel: int, bend: int) -> None: + """ + Set the pitch bend value on channel ch + """ + def playNote(self, channel: int, midiNote: int, velocity: int, detune: int = 0) -> None: + """ + Trigger a note on this Surge XT instance. + """ + def polyAftertouch(self, channel: int, key: int, value: int) -> None: + """ + Send the poly aftertouch MIDI message + """ + def process(self) -> None: + """ + Run Surge XT for one block and update the internal output buffer. + """ + def processMultiBlock(self, val: numpy.ndarray[numpy.float32], startBlock: int = 0, nBlocks: int = -1) -> None: + """ + Run the Surge XT engine for multiple blocks, updating the value in the numpy array. Either populate the + entire array, or starting at startBlock position in the output, populate nBlocks. + """ + def releaseNote(self, channel: int, midiNote: int, releaseVelocity: int = 0) -> None: + """ + Release a note on this Surge XT instance. + """ + def remapToStandardKeyboard(self) -> None: + """ + Return to standard C-centered keyboard mapping + """ + def retuneToStandardScale(self) -> None: + """ + Return this instance to 12-TET Scale + """ + def retuneToStandardTuning(self) -> None: + """ + Return this instance to 12-TET Concert Keyboard Mapping + """ + def savePatch(self, path: str) -> None: + """ + Save the current state of Surge XT to an .fxp file. + """ + def setModDepth01(self, targetParameter: SurgePyNamedParam, modulationSource: SurgePyModSource, depth: float, scene: int = 0, index: int = 0) -> None: + """ + Set a modulation to a given depth + """ + def setParamVal(self, param: SurgePyNamedParam, toThis: float) -> None: + """ + Set a parameter value + """ +class SurgeSynthesizer_ID: + def __init__(self) -> None: + ... + def __repr__(self) -> str: + ... + def getSynthSideId(self) -> int: + ... +class TuningApplicationMode: + """ + Members: + + RETUNE_ALL + + RETUNE_MIDI_ONLY + """ + RETUNE_ALL: typing.ClassVar[TuningApplicationMode] # value = + RETUNE_MIDI_ONLY: typing.ClassVar[TuningApplicationMode] # value = + __members__: typing.ClassVar[dict[str, TuningApplicationMode]] # value = {'RETUNE_ALL': , 'RETUNE_MIDI_ONLY': } + def __eq__(self, other: typing.Any) -> bool: + ... + def __getstate__(self) -> int: + ... + def __hash__(self) -> int: + ... + def __index__(self) -> int: + ... + def __init__(self, value: int) -> None: + ... + def __int__(self) -> int: + ... + def __ne__(self, other: typing.Any) -> bool: + ... + def __repr__(self) -> str: + ... + def __setstate__(self, state: int) -> None: + ... + def __str__(self) -> str: + ... + @property + def name(self) -> str: + ... + @property + def value(self) -> int: + ... +def createSurge(sampleRate: float) -> SurgeSynthesizer: + """ + Create a Surge XT instance + """ +def getVersion() -> str: + """ + Get the version of Surge XT + """ diff --git a/src/surge-python/surgepy/_surgepy/constants.pyi b/src/surge-python/surgepy/_surgepy/constants.pyi new file mode 100644 index 00000000000..343ab583fb2 --- /dev/null +++ b/src/surge-python/surgepy/_surgepy/constants.pyi @@ -0,0 +1,117 @@ +""" +Constants which are used to navigate Surge XT +""" +from __future__ import annotations +__all__ = ['adsr_ampeg', 'adsr_filteg', 'cg_ENV', 'cg_FILTER', 'cg_FX', 'cg_GLOBAL', 'cg_LFO', 'cg_MIX', 'cg_OSC', 'fc_dual1', 'fc_dual2', 'fc_ring', 'fc_serial1', 'fc_serial2', 'fc_serial3', 'fc_stereo', 'fc_wide', 'fm_2and3to1', 'fm_2to1', 'fm_3to2to1', 'fm_off', 'fxslot_ains1', 'fxslot_ains2', 'fxslot_bins1', 'fxslot_bins2', 'fxslot_global1', 'fxslot_global2', 'fxslot_send1', 'fxslot_send2', 'fxt_airwindows', 'fxt_chorus4', 'fxt_conditioner', 'fxt_delay', 'fxt_distortion', 'fxt_eq', 'fxt_flanger', 'fxt_freqshift', 'fxt_neuron', 'fxt_off', 'fxt_phaser', 'fxt_reverb', 'fxt_reverb2', 'fxt_ringmod', 'fxt_rotaryspeaker', 'fxt_vocoder', 'lt_envelope', 'lt_formula', 'lt_mseg', 'lt_noise', 'lt_ramp', 'lt_sine', 'lt_snh', 'lt_square', 'lt_tri', 'ms_aftertouch', 'ms_alternate_bipolar', 'ms_alternate_unipolar', 'ms_ampeg', 'ms_breath', 'ms_ctrl1', 'ms_ctrl2', 'ms_ctrl3', 'ms_ctrl4', 'ms_ctrl5', 'ms_ctrl6', 'ms_ctrl7', 'ms_ctrl8', 'ms_expression', 'ms_filtereg', 'ms_highest_key', 'ms_keytrack', 'ms_latest_key', 'ms_lfo1', 'ms_lfo2', 'ms_lfo3', 'ms_lfo4', 'ms_lfo5', 'ms_lfo6', 'ms_lowest_key', 'ms_modwheel', 'ms_pitchbend', 'ms_polyaftertouch', 'ms_random_bipolar', 'ms_random_unipolar', 'ms_releasevelocity', 'ms_slfo1', 'ms_slfo2', 'ms_slfo3', 'ms_slfo4', 'ms_slfo5', 'ms_slfo6', 'ms_sustain', 'ms_timbre', 'ms_velocity', 'ot_FM2', 'ot_FM3', 'ot_audioinput', 'ot_classic', 'ot_shnoise', 'ot_sine', 'ot_wavetable', 'ot_window', 'pm_latch', 'pm_mono', 'pm_mono_fp', 'pm_mono_st', 'pm_mono_st_fp', 'pm_poly', 'sm_chsplit', 'sm_dual', 'sm_single', 'sm_split'] +adsr_ampeg: int = 0 +adsr_filteg: int = 1 +cg_ENV: int = 5 +cg_FILTER: int = 4 +cg_FX: int = 7 +cg_GLOBAL: int = 0 +cg_LFO: int = 6 +cg_MIX: int = 3 +cg_OSC: int = 2 +fc_dual1: int = 3 +fc_dual2: int = 4 +fc_ring: int = 6 +fc_serial1: int = 0 +fc_serial2: int = 1 +fc_serial3: int = 2 +fc_stereo: int = 5 +fc_wide: int = 7 +fm_2and3to1: int = 3 +fm_2to1: int = 1 +fm_3to2to1: int = 2 +fm_off: int = 0 +fxslot_ains1: int = 0 +fxslot_ains2: int = 1 +fxslot_bins1: int = 2 +fxslot_bins2: int = 3 +fxslot_global1: int = 6 +fxslot_global2: int = 7 +fxslot_send1: int = 4 +fxslot_send2: int = 5 +fxt_airwindows: int = 14 +fxt_chorus4: int = 9 +fxt_conditioner: int = 8 +fxt_delay: int = 1 +fxt_distortion: int = 5 +fxt_eq: int = 6 +fxt_flanger: int = 12 +fxt_freqshift: int = 7 +fxt_neuron: int = 15 +fxt_off: int = 0 +fxt_phaser: int = 3 +fxt_reverb: int = 2 +fxt_reverb2: int = 11 +fxt_ringmod: int = 13 +fxt_rotaryspeaker: int = 4 +fxt_vocoder: int = 10 +lt_envelope: int = 6 +lt_formula: int = 9 +lt_mseg: int = 8 +lt_noise: int = 4 +lt_ramp: int = 3 +lt_sine: int = 0 +lt_snh: int = 5 +lt_square: int = 2 +lt_tri: int = 1 +ms_aftertouch: int = 4 +ms_alternate_bipolar: int = 33 +ms_alternate_unipolar: int = 34 +ms_ampeg: int = 15 +ms_breath: int = 35 +ms_ctrl1: int = 7 +ms_ctrl2: int = 8 +ms_ctrl3: int = 9 +ms_ctrl4: int = 10 +ms_ctrl5: int = 11 +ms_ctrl6: int = 12 +ms_ctrl7: int = 13 +ms_ctrl8: int = 14 +ms_expression: int = 36 +ms_filtereg: int = 16 +ms_highest_key: int = 39 +ms_keytrack: int = 2 +ms_latest_key: int = 40 +ms_lfo1: int = 17 +ms_lfo2: int = 18 +ms_lfo3: int = 19 +ms_lfo4: int = 20 +ms_lfo5: int = 21 +ms_lfo6: int = 22 +ms_lowest_key: int = 38 +ms_modwheel: int = 6 +ms_pitchbend: int = 5 +ms_polyaftertouch: int = 3 +ms_random_bipolar: int = 31 +ms_random_unipolar: int = 32 +ms_releasevelocity: int = 30 +ms_slfo1: int = 23 +ms_slfo2: int = 24 +ms_slfo3: int = 25 +ms_slfo4: int = 26 +ms_slfo5: int = 27 +ms_slfo6: int = 28 +ms_sustain: int = 37 +ms_timbre: int = 29 +ms_velocity: int = 1 +ot_FM2: int = 6 +ot_FM3: int = 5 +ot_audioinput: int = 4 +ot_classic: int = 0 +ot_shnoise: int = 3 +ot_sine: int = 1 +ot_wavetable: int = 2 +ot_window: int = 7 +pm_latch: int = 5 +pm_mono: int = 1 +pm_mono_fp: int = 3 +pm_mono_st: int = 2 +pm_mono_st_fp: int = 4 +pm_poly: int = 0 +sm_chsplit: int = 3 +sm_dual: int = 2 +sm_single: int = 0 +sm_split: int = 1