Skip to content

Commit 01dcb5c

Browse files
committed
Publish more bindings
1 parent e680b6a commit 01dcb5c

File tree

13 files changed

+2212
-2
lines changed

13 files changed

+2212
-2
lines changed

modules/yup_python/bindings/yup_YupAudioBasics_bindings.cpp

Lines changed: 564 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
==============================================================================
3+
4+
This file is part of the YUP library.
5+
Copyright (c) 2025 - [email protected]
6+
7+
YUP is an open source library subject to open-source licensing.
8+
9+
The code included in this file is provided under the terms of the ISC license
10+
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
11+
to use, copy, modify, and/or distribute this software for any purpose with or
12+
without fee is hereby granted provided that the above copyright notice and
13+
this permission notice appear in all copies.
14+
15+
YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
16+
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
17+
DISCLAIMED.
18+
19+
==============================================================================
20+
*/
21+
22+
#pragma once
23+
24+
#if ! YUP_MODULE_AVAILABLE_yup_audio_basics
25+
#error This binding file requires adding the yup_audio_basics module in the project
26+
#else
27+
#include <yup_audio_basics/yup_audio_basics.h>
28+
#endif
29+
30+
#include "yup_YupCore_bindings.h"
31+
32+
#define YUP_PYTHON_INCLUDE_PYBIND11_OPERATORS
33+
#define YUP_PYTHON_INCLUDE_PYBIND11_STL
34+
#include "../utilities/yup_PyBind11Includes.h"
35+
36+
namespace yup::Bindings
37+
{
38+
39+
//==============================================================================
40+
41+
void registerYupAudioBasicsBindings (pybind11::module_& m);
42+
43+
//==============================================================================
44+
45+
template <class Base = AudioSource>
46+
struct PyAudioSource : Base
47+
{
48+
using Base::Base;
49+
50+
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override
51+
{
52+
PYBIND11_OVERRIDE_PURE (void, Base, prepareToPlay, samplesPerBlockExpected, sampleRate);
53+
}
54+
55+
void releaseResources() override
56+
{
57+
PYBIND11_OVERRIDE_PURE (void, Base, releaseResources);
58+
}
59+
60+
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
61+
{
62+
PYBIND11_OVERRIDE_PURE (void, Base, getNextAudioBlock, bufferToFill);
63+
}
64+
};
65+
66+
//==============================================================================
67+
68+
template <class Base = PositionableAudioSource>
69+
struct PyPositionableAudioSource : PyAudioSource<Base>
70+
{
71+
using PyAudioSource<Base>::PyAudioSource;
72+
73+
void setNextReadPosition (int64 newPosition) override
74+
{
75+
PYBIND11_OVERRIDE_PURE (void, Base, setNextReadPosition, newPosition);
76+
}
77+
78+
int64 getNextReadPosition() const override
79+
{
80+
PYBIND11_OVERRIDE_PURE (int64, Base, getNextReadPosition);
81+
}
82+
83+
int64 getTotalLength() const override
84+
{
85+
PYBIND11_OVERRIDE_PURE (int64, Base, getTotalLength);
86+
}
87+
88+
bool isLooping() const override
89+
{
90+
PYBIND11_OVERRIDE_PURE (bool, Base, isLooping);
91+
}
92+
93+
void setLooping (bool shouldLoop) override
94+
{
95+
PYBIND11_OVERRIDE (void, Base, setLooping, shouldLoop);
96+
}
97+
};
98+
99+
//==============================================================================
100+
101+
struct PySynthesiserSound : SynthesiserSound
102+
{
103+
bool appliesToNote (int midiNoteNumber) override
104+
{
105+
PYBIND11_OVERRIDE_PURE (bool, SynthesiserSound, appliesToNote, midiNoteNumber);
106+
}
107+
108+
bool appliesToChannel (int midiChannel) override
109+
{
110+
PYBIND11_OVERRIDE_PURE (bool, SynthesiserSound, appliesToChannel, midiChannel);
111+
}
112+
};
113+
114+
//==============================================================================
115+
116+
struct PySynthesiserVoice : SynthesiserVoice
117+
{
118+
bool canPlaySound (SynthesiserSound* sound) override
119+
{
120+
PYBIND11_OVERRIDE_PURE (bool, SynthesiserVoice, canPlaySound, sound);
121+
}
122+
123+
void startNote (int midiNoteNumber, float velocity, SynthesiserSound* sound, int currentPitchWheelPosition) override
124+
{
125+
PYBIND11_OVERRIDE_PURE (void, SynthesiserVoice, startNote, midiNoteNumber, velocity, sound, currentPitchWheelPosition);
126+
}
127+
128+
void stopNote (float velocity, bool allowTailOff) override
129+
{
130+
PYBIND11_OVERRIDE_PURE (void, SynthesiserVoice, stopNote, velocity, allowTailOff);
131+
}
132+
133+
void pitchWheelMoved (int newPitchWheelValue) override
134+
{
135+
PYBIND11_OVERRIDE_PURE (void, SynthesiserVoice, pitchWheelMoved, newPitchWheelValue);
136+
}
137+
138+
void controllerMoved (int controllerNumber, int newControllerValue) override
139+
{
140+
PYBIND11_OVERRIDE_PURE (void, SynthesiserVoice, controllerMoved, controllerNumber, newControllerValue);
141+
}
142+
143+
void renderNextBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
144+
{
145+
PYBIND11_OVERRIDE_PURE (void, SynthesiserVoice, renderNextBlock, outputBuffer, startSample, numSamples);
146+
}
147+
148+
void setCurrentPlaybackSampleRate (double newRate) override
149+
{
150+
PYBIND11_OVERRIDE (void, SynthesiserVoice, setCurrentPlaybackSampleRate, newRate);
151+
}
152+
153+
bool isVoiceActive() const override
154+
{
155+
PYBIND11_OVERRIDE (bool, SynthesiserVoice, isVoiceActive);
156+
}
157+
};
158+
159+
//==============================================================================
160+
161+
struct PyAudioPlayHeadPositionInfo : AudioPlayHead::PositionInfo
162+
{
163+
using AudioPlayHead::PositionInfo::PositionInfo;
164+
};
165+
166+
} // namespace yup::Bindings

modules/yup_python/modules/yup_YupMain_module.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@
4040
#include "../bindings/yup_YupGui_bindings.h"
4141
#endif
4242

43-
/*
4443
#if YUP_MODULE_AVAILABLE_yup_audio_basics
4544
#include "../bindings/yup_YupAudioBasics_bindings.h"
4645
#endif
4746

47+
/*
4848
#if YUP_MODULE_AVAILABLE_yup_audio_devices
4949
#include "../bindings/yup_YupAudioDevices_bindings.h"
5050
#endif
@@ -92,11 +92,11 @@ PYBIND11_MODULE (YUP_PYTHON_MODULE_NAME, m)
9292
yup::Bindings::registerYupGuiBindings (m);
9393
#endif
9494

95-
/*
9695
#if YUP_MODULE_AVAILABLE_yup_audio_basics
9796
yup::Bindings::registerYupAudioBasicsBindings (m);
9897
#endif
9998

99+
/*
100100
#if YUP_MODULE_AVAILABLE_yup_audio_devices
101101
yup::Bindings::registerYupAudioDevicesBindings (m);
102102
#endif
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
==============================================================================
3+
4+
This file is part of the YUP library.
5+
Copyright (c) 2025 - [email protected]
6+
7+
YUP is an open source library subject to open-source licensing.
8+
9+
The code included in this file is provided under the terms of the ISC license
10+
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
11+
to use, copy, modify, and/or distribute this software for any purpose with or
12+
without fee is hereby granted provided that the above copyright notice and
13+
this permission notice appear in all copies.
14+
15+
YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
16+
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
17+
DISCLAIMED.
18+
19+
==============================================================================
20+
*/
21+
22+
#include "bindings/yup_YupAudioBasics_bindings.cpp"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Test package for yup_audio_basics bindings
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import yup
2+
3+
#==================================================================================================
4+
5+
def test_adsr_parameters_construction():
6+
# Test default construction
7+
params = yup.ADSR.Parameters()
8+
assert params.attack >= 0.0
9+
assert params.decay >= 0.0
10+
assert params.sustain >= 0.0
11+
assert params.release >= 0.0
12+
13+
# Test construction with values
14+
params = yup.ADSR.Parameters(0.1, 0.2, 0.7, 0.5)
15+
assert abs(params.attack - 0.1) < 0.001
16+
assert abs(params.decay - 0.2) < 0.001
17+
assert abs(params.sustain - 0.7) < 0.001
18+
assert abs(params.release - 0.5) < 0.001
19+
20+
#==================================================================================================
21+
22+
def test_adsr_parameters_fields():
23+
params = yup.ADSR.Parameters()
24+
25+
# Test setting fields
26+
params.attack = 0.05
27+
params.decay = 0.1
28+
params.sustain = 0.8
29+
params.release = 0.3
30+
31+
assert abs(params.attack - 0.05) < 0.001
32+
assert abs(params.decay - 0.1) < 0.001
33+
assert abs(params.sustain - 0.8) < 0.001
34+
assert abs(params.release - 0.3) < 0.001
35+
36+
#==================================================================================================
37+
38+
def test_adsr_construction():
39+
adsr = yup.ADSR()
40+
assert not adsr.isActive()
41+
42+
#==================================================================================================
43+
44+
def test_adsr_set_parameters():
45+
adsr = yup.ADSR()
46+
params = yup.ADSR.Parameters(0.1, 0.2, 0.7, 0.5)
47+
48+
adsr.setParameters(params)
49+
retrieved = adsr.getParameters()
50+
51+
assert abs(retrieved.attack - params.attack) < 0.001
52+
assert abs(retrieved.decay - params.decay) < 0.001
53+
assert abs(retrieved.sustain - params.sustain) < 0.001
54+
assert abs(retrieved.release - params.release) < 0.001
55+
56+
#==================================================================================================
57+
58+
def test_adsr_set_sample_rate():
59+
adsr = yup.ADSR()
60+
adsr.setSampleRate(44100.0)
61+
62+
# Should not throw, exact behavior depends on implementation
63+
params = yup.ADSR.Parameters(0.1, 0.2, 0.7, 0.5)
64+
adsr.setParameters(params)
65+
66+
#==================================================================================================
67+
68+
def test_adsr_note_on_off():
69+
adsr = yup.ADSR()
70+
adsr.setSampleRate(44100.0)
71+
72+
params = yup.ADSR.Parameters(0.01, 0.01, 0.7, 0.1)
73+
adsr.setParameters(params)
74+
75+
# Initially not active
76+
assert not adsr.isActive()
77+
78+
# Note on activates
79+
adsr.noteOn()
80+
assert adsr.isActive()
81+
82+
# Get some samples during attack
83+
for _ in range(100):
84+
sample = adsr.getNextSample()
85+
assert sample >= 0.0
86+
87+
# Note off starts release
88+
adsr.noteOff()
89+
90+
# Still active during release
91+
active_after_noteoff = adsr.isActive()
92+
93+
# Process through release phase
94+
for _ in range(10000):
95+
sample = adsr.getNextSample()
96+
if not adsr.isActive():
97+
break
98+
99+
# Eventually becomes inactive
100+
# (May or may not be inactive depending on release time and samples processed)
101+
102+
#==================================================================================================
103+
104+
def test_adsr_reset():
105+
adsr = yup.ADSR()
106+
adsr.setSampleRate(44100.0)
107+
108+
params = yup.ADSR.Parameters(0.01, 0.01, 0.7, 0.1)
109+
adsr.setParameters(params)
110+
111+
adsr.noteOn()
112+
assert adsr.isActive()
113+
114+
# Get some samples
115+
for _ in range(100):
116+
adsr.getNextSample()
117+
118+
# Reset should stop immediately
119+
adsr.reset()
120+
assert not adsr.isActive()
121+
122+
#==================================================================================================
123+
124+
def test_adsr_envelope_shape():
125+
adsr = yup.ADSR()
126+
adsr.setSampleRate(44100.0)
127+
128+
# Very short times for testing
129+
params = yup.ADSR.Parameters(0.001, 0.001, 0.5, 0.001)
130+
adsr.setParameters(params)
131+
132+
adsr.noteOn()
133+
134+
# Collect samples during attack phase
135+
attack_samples = []
136+
for _ in range(100):
137+
attack_samples.append(adsr.getNextSample())
138+
139+
# Attack phase should generally increase
140+
# (allowing for some tolerance due to discrete sampling)
141+
increasing_count = 0
142+
for i in range(len(attack_samples) - 1):
143+
if attack_samples[i + 1] >= attack_samples[i]:
144+
increasing_count += 1
145+
146+
# Most samples should be increasing during attack (allow some tolerance)
147+
assert increasing_count > len(attack_samples) * 0.4
148+
149+
#==================================================================================================
150+
151+
def test_adsr_sustain_level():
152+
adsr = yup.ADSR()
153+
adsr.setSampleRate(44100.0)
154+
155+
sustain_level = 0.6
156+
params = yup.ADSR.Parameters(0.0001, 0.0001, sustain_level, 0.1)
157+
adsr.setParameters(params)
158+
159+
adsr.noteOn()
160+
161+
# Process through attack and decay to reach sustain
162+
for _ in range(1000):
163+
adsr.getNextSample()
164+
165+
# Sample during sustain phase should be close to sustain level
166+
sustain_sample = adsr.getNextSample()
167+
168+
# Allow generous tolerance for sustain level
169+
assert abs(sustain_sample - sustain_level) < 0.3

0 commit comments

Comments
 (0)