Skip to content

Sweep generator #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 66 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
d0c1dca
AudioDevice base class
mberz Mar 31, 2023
49baf2a
OutputAudioDevice class implementation
mberz Mar 31, 2023
a288342
add tests
mberz Mar 31, 2023
47fda87
utils module for tests
mberz Mar 31, 2023
76bed3a
clean up
mberz Mar 31, 2023
56fc6ae
add portaudio to circleci
mberz Mar 31, 2023
d4eefd3
fix multiple buffer stub
mberz Mar 31, 2023
e53ba7e
remove input device related tests
mberz Mar 31, 2023
e818048
simple callback tests
mberz Apr 3, 2023
8942b61
don't run physical tests on circleci
mberz Apr 3, 2023
8e0a796
add tests for setting the block size
mberz Apr 4, 2023
68f4247
sampling rate setter tests
mberz Apr 4, 2023
4ec165f
fix case typo
mberz Apr 4, 2023
9c714da
buffer_size setter/getter
mberz Apr 5, 2023
cf7600f
Add Focusrite USB ASIO
twennemann May 5, 2023
b921821
Update test_devices_physical.py
twennemann May 16, 2023
9f0b8d0
Physical Device Tests
twennemann May 17, 2023
9cbd3a7
sample_rate setter
twennemann May 17, 2023
20790ea
Merge pull request #12 from pyfar/output_device_tests
mberz Sep 29, 2023
4c1b04a
Sampling_rate setter Output Device
twennemann Oct 4, 2023
57835ee
Merge branch 'feature/output_device' of https://github.com/pyfar/haio…
twennemann Oct 4, 2023
ee3001f
Resolve merging mistake
twennemann Oct 4, 2023
ee51ac7
Merge branch 'feature/generators' into feature/output_device
twennemann Oct 4, 2023
deeff8b
sampling_rate + block_size setter + tests
twennemann Oct 4, 2023
5e0bb17
update
twennemann Oct 4, 2023
8559b8a
identifier setter
twennemann Oct 4, 2023
70e9356
Channel and dtype setter
twennemann Oct 4, 2023
0b90997
initialize _stream_block_out
twennemann Oct 9, 2023
4067605
Test Setter
twennemann Oct 9, 2023
32c15b4
Revice sample_rate & block_size setter
twennemann Oct 10, 2023
365b0c6
revice sampling_rate setter
twennemann Oct 10, 2023
1803598
documentation
twennemann Oct 10, 2023
4fcf21e
Merge branch 'develop' into feature/output_device
mberz Nov 3, 2023
738d1b0
Merge branch 'develop' into feature/output_device
mberz Apr 1, 2025
069ca79
add asio support for testing physical devices on windows
mberz Apr 3, 2025
1cdb8a6
add ASIO devices to list of test devices
mberz Apr 3, 2025
a0168de
Merge branch 'develop' into feature/output_device
mberz Apr 3, 2025
b6b91ce
move sr, blocksize and dtype properties into abstract base class
mberz Apr 3, 2025
0754b3e
add private checker function if the stream is active
mberz Apr 3, 2025
3f5a3ca
minor cleanup
mberz Apr 3, 2025
18235cc
Implement class containing input and output channel mappings
mberz Apr 10, 2025
147d68b
init tests for mappings
mberz Apr 10, 2025
db52305
make ChannelMapping private
mberz Apr 11, 2025
17eb186
cleanup and type hinting for ChannelMapping
mberz Apr 11, 2025
bec6a0a
add class docstrings
mberz Apr 11, 2025
6d3ad5b
update docstrings
mberz Apr 11, 2025
c1b3088
Merge branch 'develop' into feature/output_device
mberz Apr 11, 2025
d863c57
Merge branch 'develop' into feature/output_device
mberz Apr 11, 2025
44fbcbf
adhere to ruff rules in ChannelMapping classes
mberz Apr 11, 2025
eacadb7
update imports
mberz May 9, 2025
9d3e552
update AudioDevice ABC
mberz May 9, 2025
8a96c1b
update docstrings
mberz May 9, 2025
2dc2b97
Add method to check if buffer is active
mberz May 9, 2025
d38e408
update identifier property
mberz May 9, 2025
3598897
update setter and getter
mberz May 9, 2025
6b95004
update calback
mberz May 9, 2025
1c81b26
add destructor to avoid blocking soundcards after the device is deleted
mberz May 9, 2025
fefdcbb
Do not axis alignment of data in the ChannelMapping classes
mberz May 9, 2025
0a8ce28
function to query and return a AudioDevice
mberz May 9, 2025
9af1b99
update default devices for testing
mberz May 9, 2025
d8a79ab
temporarily deactivate testing channel setters
mberz May 9, 2025
29a947f
change the frequency of the sine stub to 440 Hz
mberz May 9, 2025
4d1553a
add missing numpy import
mberz May 9, 2025
6eeda6b
cleanup import
mberz May 9, 2025
b008614
cleanup test playback
mberz May 9, 2025
cb72c1e
added buffer class for linear sweep generator and the corresponding test
Jun 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions haiopy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,14 @@
__author__ = """The pyfar developers"""
__email__ = '[email protected]'
__version__ = '0.1.0'


from . import (
buffers,
devices,
)

__all__ = [
'buffers',
'devices',
]
204 changes: 203 additions & 1 deletion haiopy/buffers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from threading import Event
from scipy import signal
import warnings

import matplotlib.pyplot as plt

class _Buffer(ABC):
"""Abstract base class for audio buffers for block-wise iteration.
Expand Down Expand Up @@ -538,3 +538,205 @@ def next(self):

def _reset(self):
super()._reset()

class LinearSweepGenerator(_Buffer):
"""
Generator to block wise calculate a linear sweep as described in [#]_.

Examples:
--------

>>> from haiopy.buffers import LinearSweepGenerator
>>> import matplotlib.pyplot as plt
>>> sine = LinearSweepGenerator(440, 128)
>>> blocks = [next(LinearSweepGenerator), next(LinearSweepGenerator), next(LinearSweepGenerator)]
>>> for block in blocks:
>>> plt.plot((block))
>>> plt.show()

Source:
.. [#] Farina, Angelo (2000): "Simultaneous measurement of impulse
response and distortion with a swept-sine technique." 108th AES
Convention, Paris: France.
"""

def __init__(self,
block_size,
amplitude=1,
sweep_duration = 10,
f_1 = 0,
f_2 = 22050,
sampling_rate=44100) -> None:
"""
Initialize a `LinearSweepGenerator`with a given block_size,
amplitude, sweep duration, lower frequency, upper frequency
and samplingrate.

Parameters
----------
block_size : int
The block size in samples.
amplitude: double, optional
The amplitude of the sine. The default is ``1``.
sweep_duration : float, optional
The duration of one sweep in seconds. The default is ``10`` s
f_1 : int, optional
The starting frequency for the sweep in Hz. The default is
``0`` Hz.
f_2: int, optional
The ending frequency for the sweep in Hz. The default is
``22050`` Hz.
sampling_rate : int, optional
The sampling rate in Hz. The default is ``44100``.
"""
super().__init__(block_size)
self._amplitude = amplitude
self._sampling_rate = sampling_rate
self._T = sweep_duration
self._f_1 = f_1

if f_2 > sampling_rate/2:
self.f_2 = np.floor(sampling_rate/2)
else:
self._f_2 = f_2

self._t_start = 0
self._in_transition = False

@property
def amplitude(self):
"""Return the amplitude of the sweep."""
return self._amplitude

@amplitude.setter
def amplitude(self, amplitude):
self.check_if_active()
self._amplitude = amplitude
self._reset()

@property
def sampling_rate(self):
"""Return the sampling rate of the generated sweep."""
return self._sampling_rate

@sampling_rate.setter
def sampling_rate(self, sampling_rate):
"""Set the sampling rate."""
self.check_if_active()
self._sampling_rate = sampling_rate
self._phase = 0

@property
def n_channels(self):
"""Return the number of channels. This is currently always 1."""
return 1

@property
def sweep_duration(self):
"""Return the duration for one sweep."""
return self._T

@sweep_duration.setter
def sweep_duration(self, sweep_duration):
"""Set the duration of one sweep."""
self.check_if_active()
self._sweep_duration = sweep_duration
self._T = sweep_duration

@property
def T(self):
"""Return the period time T for one sweep."""
return self._T

@property
def f_1(self):
"""Return lower freuqency limit for sweep."""
return self._f_1

@f_1.setter
def f_1(self, f_1):
"""Set the lower frequency of the sweep."""
self.check_if_active()
self._f_1 = f_1
self._reset()

@property
def f_2(self):
"""Return upper freuqency limit for sweep."""
return self._f_2

@f_2.setter
def f_2(self, f_2):
"""Set the upper frequency of the sweep."""
self.check_if_active()
self._f_2 = f_2
self._reset()

@property
def t_start(self):
"""Return current time."""
return self._t_start

def _set_block_size(self, block_size):
"""Set blocksize."""
self.check_if_active()
super()._set_block_size(block_size)
self._reset()

def next(self):
"""
Return the next audio block as numpy array and increase the current
time variable by one block.
If sweep ends inside a block, it fades out and a new sweep begins.
"""

# if in transition to a new sweep-start (i.e. sweep ends within
# a signal block, not directly at the end)
if self._in_transition:
time_till_end = self._T - self._t_start
n_samples_1 = int(time_till_end * self._sampling_rate)
t_1 = np.arange(n_samples_1) + self._t_start
self._reset()
n_samples_2 = self._block_size - n_samples_1
t_2 = np.arange(n_samples_2) / self._sampling_rate
t = np.concatenate((t_1, t_2))
else: # usual iteration
n_samples = int(self._block_size)
t = np.arange(n_samples) / self._sampling_rate + self._t_start

# [1, page 5]
w_1 = 2 * np.pi * self._f_1
w_2 = 2 * np.pi * self._f_2
data = (self._amplitude *
np.sin(w_1 * t + (w_2-w_1) / self._T * t**2 / 2))

self._t_start += self._block_size / self._sampling_rate

# window end of first sweep to reach zero at the end:
# ...and set t_start new:
if self._in_transition:
win_len = 11
win = np.hanning(win_len)
win_half = win[-int(win_len/2):]
idx = n_samples_1-len(win_half)
data[idx:n_samples_1] = data[idx:n_samples_1]*win_half

# set start time based on t_2:
# in next iterations t_start can be increased by block/fs again as
# usual
self._t_start = t_2[-1] + 1/self._sampling_rate
self._in_transition = False

# if we reach desired duration of one sweep, do....:
if (self._t_start >=
(self._T - (self._block_size / self._sampling_rate))):
if self._t_start == self._T:
self._reset()
else:
self._in_transition = True

return data

def _reset(self):
"""Reset sweep."""
self._t_start = 0
Loading
Loading