From f085f88835246d6597a33f7f118dc21b83521159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20M=C3=BCller?= Date: Sat, 23 Aug 2025 17:25:38 +0200 Subject: [PATCH] Audaspace: porting changes from upstream. This change introduces animated time stretching and pitch scaling. It also extends the Python API with AnimateableProperty. Note: to be used, this still needs rubberband. Credit: Kacey La --- extern/audaspace/AUTHORS | 2 +- extern/audaspace/CMakeLists.txt | 6 + extern/audaspace/bindings/C/AUD_Sequence.h | 10 - extern/audaspace/bindings/C/AUD_Sound.cpp | 50 ++- extern/audaspace/bindings/C/AUD_Sound.h | 50 ++- extern/audaspace/bindings/C/AUD_Types.h | 12 + extern/audaspace/bindings/python/PyAPI.cpp | 7 + .../bindings/python/PyAnimateableProperty.cpp | 398 ++++++++++++++++++ .../bindings/python/PyAnimateableProperty.h | 34 ++ .../bindings/python/PyDynamicMusic.cpp | 2 +- .../bindings/python/PyPlaybackManager.cpp | 2 +- extern/audaspace/bindings/python/PySound.cpp | 133 +++++- extern/audaspace/bindings/python/setup.py.in | 14 +- .../fx/AnimateableTimeStretchPitchScale.h | 123 ++++++ .../AnimateableTimeStretchPitchScaleReader.h | 72 ++++ .../include/fx/TimeStretchPitchScale.h | 6 + .../include/sequence/AnimateableProperty.h | 11 +- .../fx/AnimateableTimeStretchPitchScale.cpp | 78 ++++ ...AnimateableTimeStretchPitchScaleReader.cpp | 58 +++ .../src/fx/TimeStretchPitchScale.cpp | 5 + .../src/sequence/AnimateableProperty.cpp | 9 + 21 files changed, 1051 insertions(+), 31 deletions(-) create mode 100644 extern/audaspace/bindings/python/PyAnimateableProperty.cpp create mode 100644 extern/audaspace/bindings/python/PyAnimateableProperty.h create mode 100644 extern/audaspace/include/fx/AnimateableTimeStretchPitchScale.h create mode 100644 extern/audaspace/include/fx/AnimateableTimeStretchPitchScaleReader.h create mode 100644 extern/audaspace/src/fx/AnimateableTimeStretchPitchScale.cpp create mode 100644 extern/audaspace/src/fx/AnimateableTimeStretchPitchScaleReader.cpp diff --git a/extern/audaspace/AUTHORS b/extern/audaspace/AUTHORS index 6cec1a1697c..cdcdbb508ea 100644 --- a/extern/audaspace/AUTHORS +++ b/extern/audaspace/AUTHORS @@ -27,7 +27,7 @@ The pipewire backend and many fixes have been provided by - Sebastian Parborg -The rubberband integration (for time stretching and pitch scaling has been provided by +The rubberband integration (for time stretching and pitch scaling) has been provided by - Kacey La diff --git a/extern/audaspace/CMakeLists.txt b/extern/audaspace/CMakeLists.txt index 4333ffbf74c..02d9fb22815 100644 --- a/extern/audaspace/CMakeLists.txt +++ b/extern/audaspace/CMakeLists.txt @@ -803,10 +803,14 @@ if(WITH_RUBBERBAND) set(RUBBERBAND_SRC src/fx/TimeStretchPitchScale.cpp src/fx/TimeStretchPitchScaleReader.cpp + src/fx/AnimateableTimeStretchPitchScale.cpp + src/fx/AnimateableTimeStretchPitchScaleReader.cpp ) set(RUBBERBAND_HDR include/fx/TimeStretchPitchScale.h include/fx/TimeStretchPitchScaleReader.h + include/fx/AnimateableTimeStretchPitchScale.h + include/fx/AnimateableTimeStretchPitchScaleReader.h ) add_definitions(-DWITH_RUBBERBAND) @@ -1174,6 +1178,7 @@ endif() if(WITH_PYTHON) set(PYTHON_SRC + bindings/python/PyAnimateableProperty.cpp bindings/python/PyAPI.cpp bindings/python/PyDevice.cpp bindings/python/PyDynamicMusic.cpp @@ -1186,6 +1191,7 @@ if(WITH_PYTHON) bindings/python/PyThreadPool.cpp ) set(PYTHON_HDR + bindings/python/PyAnimateableProperty.h bindings/python/PyAPI.h bindings/python/PyDevice.h bindings/python/PyDynamicMusic.h diff --git a/extern/audaspace/bindings/C/AUD_Sequence.h b/extern/audaspace/bindings/C/AUD_Sequence.h index 57075f3a1cb..171753caf32 100644 --- a/extern/audaspace/bindings/C/AUD_Sequence.h +++ b/extern/audaspace/bindings/C/AUD_Sequence.h @@ -22,16 +22,6 @@ extern "C" { #endif -/// Possible animatable properties for Sequence Factories and Entries. -typedef enum -{ - AUD_AP_VOLUME, - AUD_AP_PANNING, - AUD_AP_PITCH, - AUD_AP_LOCATION, - AUD_AP_ORIENTATION -} AUD_AnimateablePropertyType; - /** * Creates a new sequenced sound scene. * \param fps The FPS of the scene. diff --git a/extern/audaspace/bindings/C/AUD_Sound.cpp b/extern/audaspace/bindings/C/AUD_Sound.cpp index cb3f56b39e8..9410ee03174 100644 --- a/extern/audaspace/bindings/C/AUD_Sound.cpp +++ b/extern/audaspace/bindings/C/AUD_Sound.cpp @@ -59,6 +59,7 @@ #ifdef WITH_RUBBERBAND #include "fx/TimeStretchPitchScale.h" +#include "fx/AnimateableTimeStretchPitchScale.h" #endif #include @@ -795,7 +796,7 @@ AUD_API AUD_Sound* AUD_Sound_equalize(AUD_Sound* sound, float *definition, int s #endif #ifdef WITH_RUBBERBAND -AUD_API AUD_Sound* AUD_Sound_timeStretchPitchScale(AUD_Sound* sound, double timeRatio, double pitchScale, AUD_StretcherQuality quality, bool preserveFormant) +AUD_API AUD_Sound* AUD_Sound_timeStretchPitchScale(AUD_Sound* sound, double timeRatio, double pitchScale, AUD_StretcherQuality quality, char preserveFormant) { assert(sound); try @@ -807,4 +808,51 @@ AUD_API AUD_Sound* AUD_Sound_timeStretchPitchScale(AUD_Sound* sound, double time return nullptr; } } + +AUD_API AUD_Sound* AUD_Sound_animateableTimeStretchPitchScale(AUD_Sound* sound, float fps, double timeRatio, double pitchScale, AUD_StretcherQuality quality, char preserveFormant) +{ + assert(sound); + try + { + return new AUD_Sound(new AnimateableTimeStretchPitchScale(*sound, fps, timeRatio, pitchScale, static_cast(quality), preserveFormant)); + } + catch(Exception&) + { + return nullptr; + } +} + +AUD_API void AUD_Sound_animateableTimeStretchPitchScale_setConstantRangeAnimationData(AUD_Sound* sound, AUD_AnimateablePropertyType type, int frame_start, int frame_end, + float* data) +{ + std::shared_ptr prop = std::dynamic_pointer_cast(*sound)->getAnimProperty(static_cast(type)); + prop->writeConstantRange(data, frame_start, frame_end); +} + +AUD_API void AUD_Sound_animateableTimeStretchPitchScale_setAnimationData(AUD_Sound* sound, AUD_AnimateablePropertyType type, int frame, float* data, char animated) +{ + std::shared_ptr prop = std::dynamic_pointer_cast(*sound)->getAnimProperty(static_cast(type)); + if(animated) + { + if(frame >= 0) + prop->write(data, frame, 1); + } + else + { + prop->write(data); + } +} + +AUD_API float AUD_Sound_animateableTimeStretchPitchScale_getFPS(AUD_Sound* sound) +{ + assert(sound); + return dynamic_cast(sound->get())->getFPS(); +} + +AUD_API void AUD_Sound_animateableTimeStretchPitchScale_setFPS(AUD_Sound* sound, float value) +{ + assert(sound); + dynamic_cast(sound->get())->setFPS(value); +} + #endif diff --git a/extern/audaspace/bindings/C/AUD_Sound.h b/extern/audaspace/bindings/C/AUD_Sound.h index d28e66b51c4..55a81eb5fbb 100644 --- a/extern/audaspace/bindings/C/AUD_Sound.h +++ b/extern/audaspace/bindings/C/AUD_Sound.h @@ -419,7 +419,55 @@ extern AUD_API AUD_Sound* AUD_Sound_mutable(AUD_Sound* sound); * \param preserveFormant Whether to preserve the vocal formants for the stretcher. * \return A handle of the time-stretched, pitch scaled sound. */ - extern AUD_API AUD_Sound* AUD_Sound_timeStretchPitchScale(AUD_Sound* sound, double timeRatio, double pitchScale, AUD_StretcherQuality quality, bool preserveFormant); + extern AUD_API AUD_Sound* AUD_Sound_timeStretchPitchScale(AUD_Sound* sound, double timeRatio, double pitchScale, AUD_StretcherQuality quality, char preserveFormant); + + /** + * Time-stretches and pitch scales a sound with animation support + * \param sound The handle of the sound. + * \param fps The fps + * \param timeRatio The initial factor by which to stretch or compress time. + * \param pitchScale The initial factor by which to adjust the pitch. + * \param quality The processing quality level of the stretcher. + * \param preserveFormant Whether to preserve the vocal formants for the stretcher. + * \return A handle of the time-stretched, pitch scaled sound. + */ + extern AUD_API AUD_Sound* AUD_Sound_animateableTimeStretchPitchScale(AUD_Sound* sound, float fps, double timeRatio, double pitchScale, AUD_StretcherQuality quality, + char preserveFormant); + + /** + * Writes animation data to the AnimatableTimeStretchPitchScale effect + * \param sequence The sound scene. + * \param type The type of animation data. + * \param frame_start Start of the frame range. + * \param frame_end End of the frame range. + * \param data The data to write. + */ + AUD_API void AUD_Sound_animateableTimeStretchPitchScale_setConstantRangeAnimationData(AUD_Sound* sound, AUD_AnimateablePropertyType type, int frame_start, int frame_end, + float* data); + + /** + * Writes animation data to the AnimatableTimeStretchPitchScale effect + * \param entry The sequenced entry. + * \param type The type of animation data. + * \param frame The frame this data is for. + * \param data The data to write. + * \param animated Whether the attribute is animated. + */ + extern AUD_API void AUD_Sound_animateableTimeStretchPitchScale_setAnimationData(AUD_Sound* sound, AUD_AnimateablePropertyType type, int frame, float* data, char animated); + + /** + * Sets the fps of an animated time-stretch, pitch-scaled sound. + * \param sound The sound to set the fps from. + * \param value The new fps to set. + */ + extern AUD_API void AUD_Sound_animateableTimeStretchPitchScale_setFPS(AUD_Sound* sound, AUD_AnimateablePropertyType type, int frame, float* data, char animated); + + /** + * Retrieves the fps of an animated time-stretch, pitch-scaled sound. + * \param sequence The sound to get the fps from. + * \return The fps of the sound. + */ + extern AUD_API float AUD_Sound_animateableTimeStretchPitchScale_getFPS(AUD_Sound* sequence); #endif #ifdef __cplusplus diff --git a/extern/audaspace/bindings/C/AUD_Types.h b/extern/audaspace/bindings/C/AUD_Types.h index d64481abc3a..096b21cde97 100644 --- a/extern/audaspace/bindings/C/AUD_Types.h +++ b/extern/audaspace/bindings/C/AUD_Types.h @@ -210,3 +210,15 @@ typedef enum AUD_STRETCHER_QUALITY_FAST = 1, /// Prioritize speed over audio quality AUD_STRETCHER_QUALITY_CONSISTENT = 2 /// Prioritize consistency for dynamic pitch changes } AUD_StretcherQuality; + +/// Possible animatable properties for Sequence Factories and Entries. +typedef enum +{ + AUD_AP_VOLUME, + AUD_AP_PANNING, + AUD_AP_PITCH, + AUD_AP_LOCATION, + AUD_AP_ORIENTATION, + AUD_AP_TIME_STRETCH, + AUD_AP_PITCH_SCALE +} AUD_AnimateablePropertyType; \ No newline at end of file diff --git a/extern/audaspace/bindings/python/PyAPI.cpp b/extern/audaspace/bindings/python/PyAPI.cpp index 3d18e20c2b2..d2d4c0879c5 100644 --- a/extern/audaspace/bindings/python/PyAPI.cpp +++ b/extern/audaspace/bindings/python/PyAPI.cpp @@ -14,6 +14,7 @@ * limitations under the License. ******************************************************************************/ +#include "PyAnimateableProperty.h" #include "PyAPI.h" #include "PySound.h" #include "PyHandle.h" @@ -104,6 +105,9 @@ PyInit_aud() if(!initializeSource()) return nullptr; + if(!initializeAnimateableProperty()) + return nullptr; + #ifdef WITH_CONVOLUTION if(!initializeImpulseResponse()) return nullptr; @@ -116,6 +120,7 @@ PyInit_aud() if(module == nullptr) return nullptr; + addAnimateablePropertyToModule(module); addSoundToModule(module); addHandleToModule(module); addDeviceToModule(module); @@ -141,6 +146,8 @@ PyInit_aud() PY_MODULE_ADD_CONSTANT(module, AP_PITCH); PY_MODULE_ADD_CONSTANT(module, AP_LOCATION); PY_MODULE_ADD_CONSTANT(module, AP_ORIENTATION); + PY_MODULE_ADD_CONSTANT(module, AP_TIME_STRETCH); + PY_MODULE_ADD_CONSTANT(module, AP_PITCH_SCALE); // channels constants PY_MODULE_ADD_CONSTANT(module, CHANNELS_INVALID); PY_MODULE_ADD_CONSTANT(module, CHANNELS_MONO); diff --git a/extern/audaspace/bindings/python/PyAnimateableProperty.cpp b/extern/audaspace/bindings/python/PyAnimateableProperty.cpp new file mode 100644 index 00000000000..af834301bc8 --- /dev/null +++ b/extern/audaspace/bindings/python/PyAnimateableProperty.cpp @@ -0,0 +1,398 @@ +/******************************************************************************* + * Copyright 2009-2025 Jörg Müller + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +#include "PyAnimateableProperty.h" + +#include "Exception.h" + +#include "sequence/AnimateableProperty.h" + +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION +#include + +#include + +using namespace aud; + +extern PyObject* AUDError; + +static PyObject* AnimateableProperty_new(PyTypeObject* type, PyObject* args, PyObject* kwds) +{ + AnimateablePropertyP* self = (AnimateablePropertyP*) type->tp_alloc(type, 0); + + int count; + float value; + + if(self != nullptr) + { + if(!PyArg_ParseTuple(args, "i|f:animateableProperty", &count, &value)) + return nullptr; + + try + { + if(PyTuple_Size(args) == 1) + { + self->animateableProperty = new std::shared_ptr(new aud::AnimateableProperty(count)); + } + else + { + self->animateableProperty = new std::shared_ptr(new aud::AnimateableProperty(count, value)); + } + } + catch(aud::Exception& e) + { + Py_DECREF(self); + PyErr_SetString(AUDError, e.what()); + return nullptr; + } + } + + return (PyObject*) self; +} + +static void AnimateableProperty_dealloc(AnimateablePropertyP* self) +{ + if(self->animateableProperty) + delete reinterpret_cast*>(self->animateableProperty); + Py_TYPE(self)->tp_free((PyObject*) self); +} + +static PyObject* AnimateableProperty_read(AnimateablePropertyP* self, PyObject* args) +{ + float position; + + if(!PyArg_ParseTuple(args, "f", &position)) + return nullptr; + + int count = (*reinterpret_cast*>(self->animateableProperty))->getCount(); + npy_intp dims[1] = {count}; + PyObject* np_array = PyArray_SimpleNew(1, dims, NPY_FLOAT32); + if(!np_array) + return nullptr; + + float* out = static_cast(PyArray_DATA(reinterpret_cast(np_array))); + + try + { + (*reinterpret_cast*>(self->animateableProperty))->read(position, out); + return np_array; + } + catch(aud::Exception& e) + { + Py_DECREF(np_array); + PyErr_SetString(AUDError, e.what()); + return nullptr; + } +} + +PyDoc_STRVAR(M_aud_AnimateableProperty_read_doc, ".. method:: read(position)\n\n" + " Reads the properties value at the given position.\n\n" + " :param position: The position in the animation in frames.\n" + " :type position: float\n" + " :return: A numpy array of values representing the properties value.\n" + " :rtype: :class:`numpy.ndarray`\n"); + +static PyObject* AnimateableProperty_readSingle(AnimateablePropertyP* self, PyObject* args) +{ + float position; + + if(!PyArg_ParseTuple(args, "f", &position)) + return nullptr; + + try + { + float value = (*reinterpret_cast*>(self->animateableProperty))->readSingle(position); + return Py_BuildValue("f", value); + } + catch(aud::Exception& e) + { + PyErr_SetString(AUDError, e.what()); + return nullptr; + } +} + +PyDoc_STRVAR(M_aud_AnimateableProperty_readSingle_doc, ".. method:: readSingle(position)\n\n" + " Reads the properties value at the given position, assuming there is exactly one value.\n\n" + " :param position: The position in the animation in frames.\n" + " :type position: float\n" + " :return: The value at that position.\n" + " :rtype: float\n\n"); + +static PyObject* AnimateableProperty_write(AnimateablePropertyP* self, PyObject* args) +{ + PyObject* array_obj; + int position = -1; + + if(!PyArg_ParseTuple(args, "O|i", &array_obj, &position)) + return nullptr; + + PyArrayObject* np_array = reinterpret_cast(PyArray_FROM_OTF(array_obj, NPY_FLOAT32, NPY_ARRAY_IN_ARRAY | NPY_ARRAY_FORCECAST)); + + if(!np_array) + { + PyErr_SetString(PyExc_TypeError, "data must be a numpy array of dtype float32"); + return nullptr; + } + + auto& prop = *reinterpret_cast*>(self->animateableProperty); + int prop_count = prop->getCount(); + npy_intp size = PyArray_SIZE(np_array); + + int ndim = PyArray_NDIM(np_array); + + bool valid_shape = false; + + // For 1D arrays, the total number of elements must be a multiple of the property count + if(ndim == 1) + { + valid_shape = (size % prop_count == 0); + } + // For 2D arrays, the number of elements in the second dimension must be the property count + else if(ndim == 2) + { + npy_intp* shape = PyArray_DIMS(np_array); + valid_shape = (shape[1] == prop_count); + } + + if(!valid_shape) + { + PyErr_SetString(PyExc_ValueError, "array shape is invalid: must be 1D with length multiple of property count or 2D with the last dimension equal to property count"); + Py_DECREF(np_array); + return nullptr; + } + + int count = static_cast(size / prop_count); + + if(count < 1) + { + PyErr_SetString(PyExc_ValueError, "input array must have at least 1 element"); + Py_DECREF(np_array); + return nullptr; + } + + float* data_ptr = reinterpret_cast(PyArray_DATA(np_array)); + try + { + if(position == -1) + { + if(count != 1) + { + PyErr_SetString(PyExc_ValueError, "input array must have exactly 1 element when position is not specified"); + Py_DECREF(np_array); + return nullptr; + } + prop->write(data_ptr); + } + else + { + prop->write(data_ptr, position, count); + } + } + catch(aud::Exception& e) + { + PyErr_SetString(AUDError, e.what()); + } + + Py_DECREF(np_array); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(M_aud_AnimateableProperty_write_doc, ".. method:: write(data[, position])\n\n" + " Writes the properties value.\n\n" + " If `position` is also given, the property is marked animated and\n" + " the values are written starting at `position`.\n\n" + " :param data: numpy array of float32 values.\n" + " :type data: numpy.ndarray\n" + " :param position: The starting position in frames.\n" + " :type position: int\n\n"); + +static PyObject* AnimateableProperty_writeConstantRange(AnimateablePropertyP* self, PyObject* args) +{ + PyObject* array_obj; + int position_start; + int position_end; + + if(!PyArg_ParseTuple(args, "Oii", &array_obj, &position_start, &position_end)) + return nullptr; + + PyArrayObject* np_array = reinterpret_cast(PyArray_FROM_OTF(array_obj, NPY_FLOAT32, NPY_ARRAY_IN_ARRAY | NPY_ARRAY_FORCECAST)); + if(!np_array) + { + PyErr_SetString(PyExc_TypeError, "data must be a numpy array of dtype float32"); + return nullptr; + } + + int ndim = PyArray_NDIM(np_array); + if(ndim != 1) + { + PyErr_SetString(PyExc_ValueError, "data must be a 1D numpy array"); + Py_DECREF(np_array); + return nullptr; + } + + float* data_ptr = reinterpret_cast(PyArray_DATA(np_array)); + + auto& prop = *reinterpret_cast*>(self->animateableProperty); + int prop_count = prop->getCount(); + + npy_intp size = PyArray_SIZE(np_array); + + if(size != prop_count) + { + PyErr_Format(PyExc_ValueError, "input array length (%lld) does not match property count (%d)", size, prop_count); + Py_DECREF(np_array); + return nullptr; + } + + try + { + prop->writeConstantRange(data_ptr, position_start, position_end); + } + catch(aud::Exception& e) + { + PyErr_SetString(AUDError, e.what()); + return nullptr; + } + + Py_DECREF(np_array); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(M_aud_AnimateableProperty_writeConstantRange_doc, ".. method:: writeConstantRange(data, position_start, position_end)\n\n" + " Fills the properties frame range with a constant value and marks it animated.\n\n" + " :param data: numpy array of float values representing the constant value.\n" + " :type data: numpy.ndarray\n" + " :param position_start: The start position in frames.\n" + " :type position_start: int\n" + " :param position_end: The end position in frames.\n" + " :type position_end: int\n\n"); + +static PyMethodDef AnimateableProperty_methods[] = { + + {(char*) "read", (PyCFunction) AnimateableProperty_read, METH_VARARGS, M_aud_AnimateableProperty_read_doc}, + {(char*) "readSingle", (PyCFunction) AnimateableProperty_readSingle, METH_VARARGS, M_aud_AnimateableProperty_readSingle_doc}, + {(char*) "write", (PyCFunction) AnimateableProperty_write, METH_VARARGS, M_aud_AnimateableProperty_write_doc}, + {(char*) "writeConstantRange", (PyCFunction) AnimateableProperty_writeConstantRange, METH_VARARGS, M_aud_AnimateableProperty_writeConstantRange_doc}, + {nullptr} /* Sentinel */ +}; + +static PyObject* AnimateableProperty_get_count(AnimateablePropertyP* self, void* nothing) +{ + try + { + int count = (*reinterpret_cast*>(self->animateableProperty))->getCount(); + return Py_BuildValue("i", count); + } + catch(aud::Exception& e) + { + PyErr_SetString(AUDError, e.what()); + return nullptr; + } +} + +PyDoc_STRVAR(M_aud_AnimateableProperty_count_doc, "The count of floats for a property."); + +static PyObject* AnimateableProperty_get_animated(AnimateablePropertyP* self, void* nothing) +{ + try + { + bool animated = (*reinterpret_cast*>(self->animateableProperty))->isAnimated(); + return PyBool_FromLong(animated); + } + catch(aud::Exception& e) + { + PyErr_SetString(AUDError, e.what()); + return nullptr; + } +} + +PyDoc_STRVAR(M_aud_AnimateableProperty_animated_doc, "Whether the property is animated."); + +static PyGetSetDef AnimateableProperty_properties[] = { + {(char*) "count", (getter) AnimateableProperty_get_count, nullptr, M_aud_AnimateableProperty_count_doc, nullptr}, + {(char*) "animated", (getter) AnimateableProperty_get_animated, nullptr, M_aud_AnimateableProperty_animated_doc, nullptr}, + {nullptr} /* Sentinel */ +}; + +PyDoc_STRVAR(M_aud_AnimateableProperty_doc, "An AnimateableProperty object stores an array of float values for animating sound properties (e.g. pan, volume, pitch-scale)"); + +// Note that AnimateablePropertyType name is already taken +PyTypeObject AnimateablePropertyPyType = { + PyVarObject_HEAD_INIT(nullptr, 0) "aud.AnimateableProperty", /* tp_name */ + sizeof(AnimateablePropertyP), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor) AnimateableProperty_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + M_aud_AnimateableProperty_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + AnimateableProperty_methods, /* tp_methods */ + 0, /* tp_members */ + AnimateableProperty_properties, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + AnimateableProperty_new, /* tp_new */ +}; + +AUD_API PyObject* AnimateableProperty_empty() +{ + return AnimateablePropertyPyType.tp_alloc(&AnimateablePropertyPyType, 0); +} + +AUD_API AnimateablePropertyP* checkAnimateableProperty(PyObject* animateableProperty) +{ + if(!PyObject_TypeCheck(animateableProperty, &AnimateablePropertyPyType)) + { + PyErr_SetString(PyExc_TypeError, "Object is not of type AnimateableProperty!"); + return nullptr; + } + + return (AnimateablePropertyP*) animateableProperty; +} + +bool initializeAnimateableProperty() +{ + import_array1(false); + return PyType_Ready(&AnimateablePropertyPyType) >= 0; +} + +void addAnimateablePropertyToModule(PyObject* module) +{ + Py_INCREF(&AnimateablePropertyPyType); + PyModule_AddObject(module, "AnimateableProperty", (PyObject*) &AnimateablePropertyPyType); +} diff --git a/extern/audaspace/bindings/python/PyAnimateableProperty.h b/extern/audaspace/bindings/python/PyAnimateableProperty.h new file mode 100644 index 00000000000..2a1f2e79aec --- /dev/null +++ b/extern/audaspace/bindings/python/PyAnimateableProperty.h @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright 2009-2025 Jörg Müller + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +#pragma once + +#include + +#include "Audaspace.h" + +typedef void Reference_AnimateableProperty; + +typedef struct +{ + PyObject_HEAD Reference_AnimateableProperty* animateableProperty; +} AnimateablePropertyP; + +extern AUD_API PyObject* AnimateableProperty_empty(); +extern AUD_API AnimateablePropertyP* checkAnimateableProperty(PyObject* animateableProperty); + +bool initializeAnimateableProperty(); +void addAnimateablePropertyToModule(PyObject* module); \ No newline at end of file diff --git a/extern/audaspace/bindings/python/PyDynamicMusic.cpp b/extern/audaspace/bindings/python/PyDynamicMusic.cpp index 12a83dfceaf..a58669322b1 100644 --- a/extern/audaspace/bindings/python/PyDynamicMusic.cpp +++ b/extern/audaspace/bindings/python/PyDynamicMusic.cpp @@ -98,7 +98,7 @@ PyDoc_STRVAR(M_aud_DynamicMusic_addTransition_doc, " :type end: int\n" " :arg transition: The transition sound.\n" " :type transition: :class:`Sound`\n" - " :return: false if the ini or end scenes don't exist, true othrwise.\n" + " :return: false if the ini or end scenes don't exist, true otherwise.\n" " :rtype: bool"); static PyObject * diff --git a/extern/audaspace/bindings/python/PyPlaybackManager.cpp b/extern/audaspace/bindings/python/PyPlaybackManager.cpp index 03caa816cb2..de67dbe1094 100644 --- a/extern/audaspace/bindings/python/PyPlaybackManager.cpp +++ b/extern/audaspace/bindings/python/PyPlaybackManager.cpp @@ -189,7 +189,7 @@ PyDoc_STRVAR(M_aud_PlaybackManager_get_volume_doc, " Retrieves the volume of a category.\n\n" " :arg catKey: the key of the category.\n" " :type catKey: int\n" - " :return: The volume of the cateogry.\n" + " :return: The volume of the category.\n" " :rtype: float\n\n"); static PyObject * diff --git a/extern/audaspace/bindings/python/PySound.cpp b/extern/audaspace/bindings/python/PySound.cpp index 34b1f8d577a..8bf9627ed09 100644 --- a/extern/audaspace/bindings/python/PySound.cpp +++ b/extern/audaspace/bindings/python/PySound.cpp @@ -14,6 +14,7 @@ * limitations under the License. ******************************************************************************/ +#include "PyAnimateableProperty.h" #include "PySound.h" #include "PySource.h" #include "PyThreadPool.h" @@ -66,6 +67,7 @@ #ifdef WITH_RUBBERBAND +#include "fx/AnimateableTimeStretchPitchScale.h" #include "fx/TimeStretchPitchScale.h" #endif @@ -1794,10 +1796,10 @@ Sound_binaural(Sound* self, PyObject* args) #ifdef WITH_RUBBERBAND -PyDoc_STRVAR(M_aud_Sound_timeStretchPitchScale_doc, ".. method:: timeStretchPitchScale(time_ratio, pitch_scale, quality, preserve_formant)\n\n" +PyDoc_STRVAR(M_aud_Sound_timeStretchPitchScale_doc, ".. method:: timeStretchPitchScale(time_stretch, pitch_scale, quality, preserve_formant)\n\n" " Applies time-stretching and pitch-scaling to the sound.\n\n" - " :arg time_ratio: The factor by which to stretch or compress time.\n" - " :type time_ratio: float\n" + " :arg time_stretch: The factor by which to stretch or compress time.\n" + " :type time_stretch: float\n" " :arg pitch_scale: The factor by which to adjust the pitch.\n" " :type pitch_scale: float\n" " :arg quality: Rubberband stretcher quality (STRETCHER_QUALITY_*).\n" @@ -1806,18 +1808,22 @@ PyDoc_STRVAR(M_aud_Sound_timeStretchPitchScale_doc, ".. method:: timeStretchPitc " :type preserve_formant: bool\n" " :return: The created :class:`Sound` object.\n" " :rtype: :class:`Sound`"); -static PyObject * -Sound_timeStretchPitchScale(Sound* self, PyObject* args) +static PyObject* Sound_timeStretchPitchScale(Sound* self, PyObject* args, PyObject* kwds) { - double time_ratio, pitch_scale; + double time_stretch = 1.0; + double pitch_scale = 1.0; int quality = 0; int preserve_formant = 0; - if(!PyArg_ParseTuple(args, "dd|i|p:timeStretchPitchScale", &time_ratio, &pitch_scale, &quality, &preserve_formant)) + static const char* kwlist[] = {"time_stretch", "pitch_scale", "quality", "preserve_formant", nullptr}; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, "|ddip:timeStretchPitchScale", const_cast(kwlist), &time_stretch, &pitch_scale, &quality, &preserve_formant)) + { return nullptr; + } if(quality < 0 || quality > 2) { - PyErr_WarnEx(PyExc_UserWarning, "Invalid quality value: using default (0 = HIGH)", 1); + PyErr_WarnEx(PyExc_UserWarning, "Invalid quality value: using default (0 = STRETCHER_QUALITY_HIGH)", 1); quality = 0; } @@ -1828,7 +1834,7 @@ Sound_timeStretchPitchScale(Sound* self, PyObject* args) { try { - parent->sound = new std::shared_ptr(new TimeStretchPitchScale(*reinterpret_cast*>(self->sound), time_ratio, pitch_scale, + parent->sound = new std::shared_ptr(new TimeStretchPitchScale(*reinterpret_cast*>(self->sound), time_stretch, pitch_scale, static_cast(quality), preserve_formant != 0)); } catch(Exception& e) @@ -1842,7 +1848,109 @@ Sound_timeStretchPitchScale(Sound* self, PyObject* args) return (PyObject*)parent; } +PyDoc_STRVAR(M_aud_Sound_animateableTimeStretchPitchScale_doc, ".. method:: animateableTimeStretchPitchScale(fps[, time_stretch, pitch_scale, quality, preserve_formant])\n\n" + " Applies time-stretching and pitch-scaling to the sound.\n\n" + " :arg fps: The FPS of the animation system.\n" + " :type fps float\n" + " :arg time_stretch: The factor by which to stretch or compress time.\n" + " :type time_stretch: float or :class:`AnimateablePropertyP`\n" + " :arg pitch_scale: The factor by which to adjust the pitch.\n" + " :type pitch_scale: float or :class:`AnimateablePropertyP`\n " + " :arg quality: Rubberband stretcher quality (STRETCHER_QUALITY_*).\n" + " :type quality: int\n" + " :arg preserve_formant: Whether to preserve the vocal formants during pitch-shifting.\n" + " :type preserve_formant: bool\n" + " :return: The created :class:`Sound` object.\n" + " :rtype: :class:`Sound`"); +static PyObject* Sound_animateableTimeStretchPitchScale(Sound* self, PyObject* args, PyObject* kwds) +{ + float fps; + PyObject* object1 = Py_None; + PyObject* object2 = Py_None; + int quality = 0; + int preserve_formant = 0; + + static const char* kwlist[] = {"fps", "time_stretch", "pitch_scale", "quality", "preserve_formant", nullptr}; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, "f|OOip:animateableTimeStretchPitchScale", const_cast(kwlist), &fps, &object1, &object2, &quality, &preserve_formant)) + { + return nullptr; + } + + std::shared_ptr time_stretch; + std::shared_ptr pitch_scale; + + if(fps <= 0) + { + PyErr_SetString(PyExc_ValueError, "FPS must be greater 0!"); + return nullptr; + } + + if(object1 == Py_None) + { + time_stretch = std::make_shared(1, 1.0); + } + else if(PyNumber_Check(object1)) + { + time_stretch = std::make_shared(1, PyFloat_AsDouble(object1)); + } + else + { + AnimateablePropertyP* time_stretch_prop = checkAnimateableProperty(object1); + if(!time_stretch_prop) + { + return nullptr; + } + time_stretch = *reinterpret_cast*>(time_stretch_prop->animateableProperty); + } + + if(object2 == Py_None) + { + pitch_scale = std::make_shared(1, 1.0); + } + else if(PyNumber_Check(object2)) + { + pitch_scale = std::make_shared(1, PyFloat_AsDouble(object2)); + } + else + { + AnimateablePropertyP* pitch_scale_prop = checkAnimateableProperty(object2); + if(!pitch_scale_prop) + { + return nullptr; + } + pitch_scale = *reinterpret_cast*>(pitch_scale_prop->animateableProperty); + } + + if(quality < 0 || quality > 2) + { + PyErr_WarnEx(PyExc_UserWarning, "Invalid quality value: using default (0 = STRETCHER_QUALITY_HIGH)", 1); + quality = 0; + } + + PyTypeObject* type = Py_TYPE(self); + Sound* parent = (Sound*) type->tp_alloc(type, 0); + + if(parent != nullptr) + { + try + { + parent->sound = new std::shared_ptr(new AnimateableTimeStretchPitchScale(*reinterpret_cast*>(self->sound), fps, time_stretch, + pitch_scale, static_cast(quality), preserve_formant != 0)); + } + catch(Exception& e) + { + Py_DECREF(parent); + PyErr_SetString(AUDError, e.what()); + return nullptr; + } + } + + return (PyObject*) parent; +} + #endif + static PyMethodDef Sound_methods[] = { {"data", (PyCFunction)Sound_data, METH_NOARGS, M_aud_Sound_data_doc @@ -1958,9 +2066,10 @@ static PyMethodDef Sound_methods[] = { }, #endif #ifdef WITH_RUBBERBAND - {"timeStretchPitchScale", (PyCFunction)Sound_timeStretchPitchScale, METH_VARARGS, - M_aud_Sound_timeStretchPitchScale_doc - }, + {"timeStretchPitchScale", (PyCFunction)Sound_timeStretchPitchScale, METH_VARARGS | METH_KEYWORDS, + M_aud_Sound_timeStretchPitchScale_doc}, + {"animateableTimeStretchPitchScale", (PyCFunction) Sound_animateableTimeStretchPitchScale, METH_VARARGS | METH_KEYWORDS, + M_aud_Sound_animateableTimeStretchPitchScale_doc}, #endif {nullptr} /* Sentinel */ }; diff --git a/extern/audaspace/bindings/python/setup.py.in b/extern/audaspace/bindings/python/setup.py.in index 0feb54503f3..f67b7f392ad 100644 --- a/extern/audaspace/bindings/python/setup.py.in +++ b/extern/audaspace/bindings/python/setup.py.in @@ -36,6 +36,14 @@ if sys.platform == 'win32': else: extra_args.append('-std=c++17') +macros = [] + +if '@WITH_FFTW@' == 'ON': + macros.append(('WITH_CONVOLUTION', None)) + +if '@WITH_RUBBERBAND@' == 'ON': + macros.append(('WITH_RUBBERBAND', None)) + audaspace = Extension( 'aud', include_dirs = ['@CMAKE_CURRENT_BINARY_DIR@', os.path.join(source_directory, '../../include'), numpy.get_include()] + (['@FFTW_INCLUDE_DIR@'] if '@WITH_FFTW@' == 'ON' else []), @@ -43,8 +51,8 @@ audaspace = Extension( library_dirs = ['.', 'Release', 'Debug'], language = 'c++', extra_compile_args = extra_args, - define_macros = [('WITH_CONVOLUTION', None)] if '@WITH_FFTW@' == 'ON' else [], - sources = [os.path.join(source_directory, file) for file in ['PyAPI.cpp', 'PyDevice.cpp', 'PyHandle.cpp', 'PySound.cpp', 'PySequenceEntry.cpp', 'PySequence.cpp', 'PyPlaybackManager.cpp', 'PyDynamicMusic.cpp', 'PyThreadPool.cpp', 'PySource.cpp'] + (['PyImpulseResponse.cpp', 'PyHRTF.cpp'] if '@WITH_FFTW@' == 'ON' else [])] + define_macros = macros, + sources = [os.path.join(source_directory, file) for file in ['PyAnimateableProperty.cpp', 'PyAPI.cpp', 'PyDevice.cpp', 'PyHandle.cpp', 'PySound.cpp', 'PySequenceEntry.cpp', 'PySequence.cpp', 'PyPlaybackManager.cpp', 'PyDynamicMusic.cpp', 'PyThreadPool.cpp', 'PySource.cpp'] + (['PyImpulseResponse.cpp', 'PyHRTF.cpp'] if '@WITH_FFTW@' == 'ON' else [])] ) setup( @@ -57,6 +65,6 @@ setup( license = 'Apache License 2.0', long_description = codecs.open(os.path.join(source_directory, '../../README.md'), 'r', 'utf-8').read(), ext_modules = [audaspace], - headers = [os.path.join(source_directory, file) for file in ['PyAPI.h', 'PyDevice.h', 'PyHandle.h', 'PySound.h', 'PySequenceEntry.h', 'PySequence.h', 'PyPlaybackManager.h', 'PyDynamicMusic.h', 'PyThreadPool.h', 'PySource.h'] + (['PyImpulseResponse.h', 'PyHRTF.h'] if '@WITH_FFTW@' == 'ON' else [])] + ['Audaspace.h'] + headers = [os.path.join(source_directory, file) for file in ['PyAnimateableProperty.h', 'PyAPI.h', 'PyDevice.h', 'PyHandle.h', 'PySound.h', 'PySequenceEntry.h', 'PySequence.h', 'PyPlaybackManager.h', 'PyDynamicMusic.h', 'PyThreadPool.h', 'PySource.h'] + (['PyImpulseResponse.h', 'PyHRTF.h'] if '@WITH_FFTW@' == 'ON' else [])] + ['Audaspace.h'] ) diff --git a/extern/audaspace/include/fx/AnimateableTimeStretchPitchScale.h b/extern/audaspace/include/fx/AnimateableTimeStretchPitchScale.h new file mode 100644 index 00000000000..76dbc14c575 --- /dev/null +++ b/extern/audaspace/include/fx/AnimateableTimeStretchPitchScale.h @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright 2009-2025 Jörg Müller + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +#pragma once + +/** + * @file AnimateableTimeStretchPitchScale.h + * @ingroup fx + * The AnimateableTimeStretchPitchScale class. + */ + +#include "fx/Effect.h" +#include "fx/TimeStretchPitchScale.h" +#include "sequence/AnimateableProperty.h" + +AUD_NAMESPACE_BEGIN + +/** + * This sound allows a sound to be time-stretched and pitch scaled with animation support + * \note The reader has to be seekable. + */ +class AUD_API AnimateableTimeStretchPitchScale : public Effect +{ +private: + /** + * The FPS of the animation system. + */ + float m_fps; + + /** + * The animateable time-stretch property. + */ + std::shared_ptr m_timeStretch; + + /** + * The animateable pitch-scale property. + */ + std::shared_ptr m_pitchScale; + + /** + * Rubberband stretcher quality options. + */ + StretcherQuality m_quality; + + /** + * Whether to preserve the vocal formants for the stretcher. + */ + bool m_preserveFormant; + + // delete copy constructor and operator= + AnimateableTimeStretchPitchScale(const AnimateableTimeStretchPitchScale&) = delete; + AnimateableTimeStretchPitchScale& operator=(const AnimateableTimeStretchPitchScale&) = delete; + +public: + /** + * Creates a new time-stretch, pitch-scaled sound that can be animated. + * \param sound The input sound. + * \param fps The fps of the animation system. + * \param timeRatio The starting factor by which to stretch or compress time. + * \param pitchScale The starting factor by which to adjust the pitch. + * \param quality The processing quality level of the stretcher. + * \param preserveFormant Whether to preserve the vocal formants for the stretcher. + */ + AnimateableTimeStretchPitchScale(std::shared_ptr sound, float fps, float timeStretch, float pitchScale, StretcherQuality quality, bool preserveFormant); + + /** + * Creates a new time-stretch, pitch-scaled sound that can be animated. + * \param sound The input sound. + * \param fps The fps of the anumation system. + * \param timeRatio The animateable time-stretch property. + * \param pitchScale The animateable pitch-scale property. + * \param quality The processing quality level of the stretcher. + * \param preserveFormant Whether to preserve the vocal formants for the stretcher. + */ + AnimateableTimeStretchPitchScale(std::shared_ptr sound, float fps, std::shared_ptr timeStretch, std::shared_ptr pitchScale, + StretcherQuality quality, bool preserveFormant); + + /** + * Returns whether formant preservation is enabled. + */ + bool getPreserveFormant() const; + + /** + * Returns the quality of the stretcher. + */ + StretcherQuality getStretcherQuality() const; + + /** + * Retrieves one of the animated properties of the sound. + * \param type Which animated property to retrieve. + * \return A shared pointer to the animated property + */ + std::shared_ptr getAnimProperty(AnimateablePropertyType type); + + /** + * Retrieves the animation system's FPS. + * \return The animation system's FPS. + */ + float getFPS() const; + + /** + * Sets the animation system's FPS. + * \param fps The new FPS. + */ + void setFPS(float fps); + + virtual std::shared_ptr createReader(); +}; + +AUD_NAMESPACE_END \ No newline at end of file diff --git a/extern/audaspace/include/fx/AnimateableTimeStretchPitchScaleReader.h b/extern/audaspace/include/fx/AnimateableTimeStretchPitchScaleReader.h new file mode 100644 index 00000000000..13a4d5d9134 --- /dev/null +++ b/extern/audaspace/include/fx/AnimateableTimeStretchPitchScaleReader.h @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright 2009-2025 Jörg Müller + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +#pragma once + +/** + * @file AnimateableTimeStretchPitchScaleReader.h + * @ingroup fx + * The AnimateableTimeStretchPitchScaleReader class. + */ +#include "fx/AnimateableTimeStretchPitchScale.h" +#include "fx/TimeStretchPitchScaleReader.h" + +AUD_NAMESPACE_BEGIN + +/** + * This class reads from another reader and applies time-stretching and pitch scaling with support for animating both properties. + */ +class AUD_API AnimateableTimeStretchPitchScaleReader : public TimeStretchPitchScaleReader +{ +private: + /** + * The FPS of the animation system. + */ + float m_fps; + + /** + * The animateable time-stretch property. + */ + std::shared_ptr m_timeStretch; + + /** + * The animateable pitch-scale property. + */ + std::shared_ptr m_pitchScale; + + // delete copy constructor and operator= + AnimateableTimeStretchPitchScaleReader(const AnimateableTimeStretchPitchScaleReader&) = delete; + AnimateableTimeStretchPitchScaleReader& operator=(const AnimateableTimeStretchPitchScaleReader&) = delete; + +public: + /** + * Creates a new animateable time-stretch, pitch scale reader. + * \param reader The input reader. + * \param fps The FPS of the animation system. + * \param timeStretch The animateable time-stretch property. + * \param pitchScale The animateable pitch-scale property. + * \param quality The stretcher quality options. + * \param preserveFormant Whether to preserve vocal formants. + */ + AnimateableTimeStretchPitchScaleReader(std::shared_ptr reader, float fp, std::shared_ptr timeStretch, + std::shared_ptr pitchScale, StretcherQuality quality, bool preserveFormant); + + virtual void read(int& length, bool& eos, sample_t* buffer) override; + + virtual void seek(int position) override; +}; + +AUD_NAMESPACE_END \ No newline at end of file diff --git a/extern/audaspace/include/fx/TimeStretchPitchScale.h b/extern/audaspace/include/fx/TimeStretchPitchScale.h index 397890791b7..9bb93cca9ea 100644 --- a/extern/audaspace/include/fx/TimeStretchPitchScale.h +++ b/extern/audaspace/include/fx/TimeStretchPitchScale.h @@ -89,6 +89,12 @@ public: * Returns whether formant preservation is enabled. */ bool getPreserveFormant() const; + + /** + * Returns the quality of the stretcher. + */ + StretcherQuality getStretcherQuality() const; + virtual std::shared_ptr createReader(); }; diff --git a/extern/audaspace/include/sequence/AnimateableProperty.h b/extern/audaspace/include/sequence/AnimateableProperty.h index d14990b6867..96bb84bd1b2 100644 --- a/extern/audaspace/include/sequence/AnimateableProperty.h +++ b/extern/audaspace/include/sequence/AnimateableProperty.h @@ -37,7 +37,9 @@ enum AnimateablePropertyType AP_PANNING, AP_PITCH, AP_LOCATION, - AP_ORIENTATION + AP_ORIENTATION, + AP_TIME_STRETCH, + AP_PITCH_SCALE }; /** @@ -127,6 +129,13 @@ public: */ void read(float position, float* out); + /** + * Reads the property's value at the specified position, assuming there is exactly one value + * \param position The position in the animation in frames. + * \return The value at the position. + */ + float readSingle(float position); + /** * Returns whether the property is animated. * \return Whether the property is animated. diff --git a/extern/audaspace/src/fx/AnimateableTimeStretchPitchScale.cpp b/extern/audaspace/src/fx/AnimateableTimeStretchPitchScale.cpp new file mode 100644 index 00000000000..d3f6276621c --- /dev/null +++ b/extern/audaspace/src/fx/AnimateableTimeStretchPitchScale.cpp @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright 2009-2025 Jörg Müller + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +#include "fx/AnimateableTimeStretchPitchScale.h" + +#include "fx/AnimateableTimeStretchPitchScaleReader.h" + +AUD_NAMESPACE_BEGIN + +AnimateableTimeStretchPitchScale::AnimateableTimeStretchPitchScale(std::shared_ptr sound, float fps, float timeStretch, float pitchScale, StretcherQuality quality, + bool preserveFormant) : + Effect(sound), + m_fps(fps), + m_timeStretch(std::make_shared(1, timeStretch)), + m_pitchScale(std::make_shared(1, pitchScale)), + m_quality(quality), + m_preserveFormant(preserveFormant) +{ +} + +AnimateableTimeStretchPitchScale::AnimateableTimeStretchPitchScale(std::shared_ptr sound, float fps, std::shared_ptr timeStretch, + std::shared_ptr pitchScale, StretcherQuality quality, bool preserveFormant) : + Effect(sound), m_fps(fps), m_timeStretch(timeStretch), m_pitchScale(pitchScale), m_quality(quality), m_preserveFormant(preserveFormant) +{ +} + +std::shared_ptr AnimateableTimeStretchPitchScale::createReader() +{ + return std::make_shared(getReader(), m_fps, m_timeStretch, m_pitchScale, m_quality, m_preserveFormant); +} + +bool AnimateableTimeStretchPitchScale::getPreserveFormant() const +{ + return m_preserveFormant; +} + +StretcherQuality AnimateableTimeStretchPitchScale::getStretcherQuality() const +{ + return m_quality; +} + +std::shared_ptr AnimateableTimeStretchPitchScale::getAnimProperty(AnimateablePropertyType type) +{ + switch(type) + { + case AP_TIME_STRETCH: + return m_timeStretch; + case AP_PITCH_SCALE: + return m_pitchScale; + default: + return nullptr; + } +} + +float AnimateableTimeStretchPitchScale::getFPS() const +{ + return m_fps; +} + +void AnimateableTimeStretchPitchScale::setFPS(float fps) +{ + m_fps = fps; +} + +AUD_NAMESPACE_END \ No newline at end of file diff --git a/extern/audaspace/src/fx/AnimateableTimeStretchPitchScaleReader.cpp b/extern/audaspace/src/fx/AnimateableTimeStretchPitchScaleReader.cpp new file mode 100644 index 00000000000..aa10151e948 --- /dev/null +++ b/extern/audaspace/src/fx/AnimateableTimeStretchPitchScaleReader.cpp @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright 2009-2025 Jörg Müller + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +#include "fx/AnimateableTimeStretchPitchScaleReader.h" + +#include "IReader.h" + +AUD_NAMESPACE_BEGIN + +AnimateableTimeStretchPitchScaleReader::AnimateableTimeStretchPitchScaleReader(std::shared_ptr reader, float fps, std::shared_ptr timeStretch, + std::shared_ptr pitchScale, StretcherQuality quality, bool preserveFormant) : + TimeStretchPitchScaleReader(reader, timeStretch->readSingle(0), pitchScale->readSingle(0), quality, preserveFormant), + m_fps(fps), + m_timeStretch(timeStretch), + m_pitchScale(pitchScale) +{ +} + +void AnimateableTimeStretchPitchScaleReader::read(int& length, bool& eos, sample_t* buffer) +{ + int position = getPosition(); + + double time = double(position) / double(m_reader->getSpecs().rate); + float frame = time * m_fps; + + float timeRatio = m_timeStretch->readSingle(frame); + setTimeRatio(timeRatio); + float pitchScale = m_pitchScale->readSingle(frame); + setPitchScale(pitchScale); + TimeStretchPitchScaleReader::read(length, eos, buffer); +} + +void AnimateableTimeStretchPitchScaleReader::seek(int position) +{ + double time = double(position) / double(m_reader->getSpecs().rate); + float frame = time * m_fps; + + float timeRatio = m_timeStretch->readSingle(frame); + setTimeRatio(timeRatio); + float pitchScale = m_pitchScale->readSingle(frame); + setPitchScale(pitchScale); + TimeStretchPitchScaleReader::seek(position); +} + +AUD_NAMESPACE_END diff --git a/extern/audaspace/src/fx/TimeStretchPitchScale.cpp b/extern/audaspace/src/fx/TimeStretchPitchScale.cpp index a3c34a84fa5..9aadce91d4d 100644 --- a/extern/audaspace/src/fx/TimeStretchPitchScale.cpp +++ b/extern/audaspace/src/fx/TimeStretchPitchScale.cpp @@ -45,4 +45,9 @@ bool TimeStretchPitchScale::getPreserveFormant() const { return m_preserveFormant; } + +StretcherQuality TimeStretchPitchScale::getStretcherQuality() const +{ + return m_quality; +} AUD_NAMESPACE_END \ No newline at end of file diff --git a/extern/audaspace/src/sequence/AnimateableProperty.cpp b/extern/audaspace/src/sequence/AnimateableProperty.cpp index b38827f5cc7..f9f919ea772 100644 --- a/extern/audaspace/src/sequence/AnimateableProperty.cpp +++ b/extern/audaspace/src/sequence/AnimateableProperty.cpp @@ -16,6 +16,7 @@ #include "sequence/AnimateableProperty.h" +#include #include #include #include @@ -228,6 +229,14 @@ void AnimateableProperty::read(float position, float* out) } } +float AnimateableProperty::readSingle(float position) +{ + assert(m_count == 1); + float value; + read(position, &value); + return value; +} + bool AnimateableProperty::isAnimated() const { return m_isAnimated;