Skip to content

Commit 1d4f930

Browse files
committed
feat: refactor parallel audio processing with improved type hints and modularity
## CHANGES - Upgrade version from 0.5.3 to 0.6.0 - Remove Python version file (.python-version) - Modernize type hints using PEP 585 syntax - Add audio directory to package exclusions - Refactor parallel processing with helper functions - Improve code organization with clearer function boundaries - Add comprehensive docstrings to new functions - Fix import order for better code readability - Add "jsonschema" to spell check dictionary
1 parent 8f7cf1d commit 1d4f930

File tree

5 files changed

+101
-31
lines changed

5 files changed

+101
-31
lines changed

.python-version

Lines changed: 0 additions & 1 deletion
This file was deleted.

binaural_generator/core/parallel.py

Lines changed: 95 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22

33
import concurrent.futures
44
import logging
5-
from typing import Any, Optional, Tuple, List
5+
from typing import Any, Optional
66

77
import numpy as np
88

99
from binaural_generator.core.data_types import AudioStep, NoiseConfig
1010
from binaural_generator.core.exceptions import AudioGenerationError, ConfigurationError
1111
from binaural_generator.core.noise import NoiseFactory, NoiseStrategy
1212
from binaural_generator.core.tone_generator import (
13-
mix_beats_and_noise, # Import the public mixer
1413
_process_beat_step,
1514
config_step_to_audio_step,
1615
generate_tone,
16+
mix_beats_and_noise,
1717
)
1818

1919
logger = logging.getLogger(__name__)
@@ -27,7 +27,7 @@ def generate_step_in_parallel(
2727
previous_freq: Optional[float],
2828
*,
2929
title: str = "Binaural Beat",
30-
) -> Tuple[int, np.ndarray, np.ndarray, float, float]:
30+
) -> tuple[int, np.ndarray, np.ndarray, float, float]:
3131
"""Generate audio for a single step, to be used in parallel processing.
3232
3333
This function adapts _process_beat_step for concurrent execution.
@@ -97,7 +97,7 @@ def _submit_tone_generation_tasks(
9797
base_freq: float,
9898
*,
9999
title: str = "Binaural Beat",
100-
) -> list[Tuple[int, concurrent.futures.Future, float, float]]:
100+
) -> list[tuple[int, concurrent.futures.Future, float, float]]:
101101
"""Submit tone generation tasks to the thread pool."""
102102
futures_context = []
103103
for idx, audio_step in enumerate(audio_steps, start=1):
@@ -111,7 +111,7 @@ def _submit_noise_task(
111111
executor: concurrent.futures.ThreadPoolExecutor,
112112
noise_config: NoiseConfig,
113113
total_num_samples: int,
114-
) -> Optional[Tuple[concurrent.futures.Future, NoiseStrategy]]:
114+
) -> Optional[tuple[concurrent.futures.Future, NoiseStrategy]]:
115115
"""Submits the noise generation task if needed."""
116116
if (
117117
noise_config.type == "none"
@@ -137,9 +137,9 @@ def _submit_noise_task(
137137

138138
def _collect_beat_results(
139139
beat_futures_with_context: list[
140-
Tuple[int, concurrent.futures.Future, float, float]
140+
tuple[int, concurrent.futures.Future, float, float]
141141
],
142-
) -> list[Tuple[int, np.ndarray, np.ndarray, float, float]]:
142+
) -> list[tuple[int, np.ndarray, np.ndarray, float, float]]:
143143
"""Collect results from beat futures, wait for completion, sort by index."""
144144
results = []
145145
future_to_context = {
@@ -162,7 +162,7 @@ def _collect_beat_results(
162162

163163

164164
def _collect_noise_result(
165-
noise_task: Optional[Tuple[concurrent.futures.Future, NoiseStrategy]],
165+
noise_task: Optional[tuple[concurrent.futures.Future, NoiseStrategy]],
166166
noise_config: NoiseConfig,
167167
) -> Optional[np.ndarray]:
168168
"""Waits for and collects the noise generation result if the task was submitted."""
@@ -184,7 +184,7 @@ def _collect_noise_result(
184184

185185

186186
def _combine_audio_segments(
187-
step_results: list[Tuple[int, np.ndarray, np.ndarray, float, float]],
187+
step_results: list[tuple[int, np.ndarray, np.ndarray, float, float]],
188188
) -> tuple[np.ndarray, np.ndarray, float]:
189189
"""Combine audio segments into continuous channels."""
190190
if not step_results:
@@ -207,18 +207,17 @@ def _combine_audio_segments(
207207
return left_channel, right_channel, total_duration
208208

209209

210-
# Removed _mix_noise_if_present as mix_beats_and_noise is now imported and used directly
211-
212210
def _execute_parallel_tasks(
213211
audio_steps: list[AudioStep],
214212
noise_config: NoiseConfig,
215213
sample_rate: int,
216214
base_freq: float,
217215
total_num_samples: int,
218-
title: str,
219-
max_workers: Optional[int],
220-
) -> Tuple[
221-
List[Tuple[int, np.ndarray, np.ndarray, float, float]], Optional[np.ndarray]
216+
*,
217+
title: str = "Binaural Beat",
218+
max_workers: Optional[int] = None,
219+
) -> tuple[
220+
list[tuple[int, np.ndarray, np.ndarray, float, float]], Optional[np.ndarray]
222221
]:
223222
"""Executes beat and noise generation tasks in parallel using a thread pool."""
224223
logger.info("Starting parallel generation of beats and noise...")
@@ -243,16 +242,11 @@ def generate_audio_sequence_parallel(
243242
*,
244243
title: str = "Binaural Beat",
245244
max_workers: Optional[int] = None,
246-
) -> Tuple[np.ndarray, np.ndarray, float]:
245+
) -> tuple[np.ndarray, np.ndarray, float]:
247246
"""Generates the complete stereo audio sequence in parallel, including noise."""
248-
logger.info("Preparing audio steps for parallel generation...")
249-
audio_steps = prepare_audio_steps(steps)
250-
251-
total_duration = sum(step.duration for step in audio_steps)
252-
total_num_samples = int(sample_rate * total_duration)
253-
logger.debug(
254-
"Total duration: %.2f s, Total samples: %d", total_duration, total_num_samples
255-
)
247+
# Prepare audio steps and calculate duration
248+
duration_info = _prepare_and_calculate_duration(steps, sample_rate)
249+
audio_steps, total_duration, total_num_samples = duration_info
256250

257251
# Execute tasks in parallel
258252
step_results, noise_signal = _execute_parallel_tasks(
@@ -261,11 +255,63 @@ def generate_audio_sequence_parallel(
261255
sample_rate,
262256
base_freq,
263257
total_num_samples,
264-
title,
265-
max_workers,
258+
title=title,
259+
max_workers=max_workers,
260+
)
261+
262+
# Process audio segments
263+
left_final, right_final = _process_audio_segments(
264+
step_results, noise_signal, noise_config, total_duration
266265
)
267266

268-
# Combine and Mix Sequentially
267+
return left_final, right_final, total_duration
268+
269+
270+
def _prepare_and_calculate_duration(
271+
steps: list[dict[str, Any]], sample_rate: int
272+
) -> tuple[list[AudioStep], float, int]:
273+
"""Prepare audio steps and calculate total duration.
274+
275+
Args:
276+
steps: List of step configuration dictionaries
277+
sample_rate: Audio sample rate in Hz
278+
279+
Returns:
280+
tuple containing:
281+
- List of prepared AudioStep objects
282+
- Total duration in seconds
283+
- Total number of samples
284+
"""
285+
logger.info("Preparing audio steps for parallel generation...")
286+
audio_steps = prepare_audio_steps(steps)
287+
288+
total_duration = sum(step.duration for step in audio_steps)
289+
total_num_samples = int(sample_rate * total_duration)
290+
logger.debug(
291+
"Total duration: %.2f s, Total samples: %d", total_duration, total_num_samples
292+
)
293+
294+
return audio_steps, total_duration, total_num_samples
295+
296+
297+
def _process_audio_segments(
298+
step_results: list[tuple[int, np.ndarray, np.ndarray, float, float]],
299+
noise_signal: Optional[np.ndarray],
300+
noise_config: NoiseConfig,
301+
total_duration: float,
302+
) -> tuple[np.ndarray, np.ndarray]:
303+
"""Process audio segments by combining and mixing with noise if needed.
304+
305+
Args:
306+
step_results: List of step result tuples
307+
noise_signal: Optional noise signal to mix
308+
noise_config: Noise configuration parameters
309+
total_duration: Total duration in seconds (for validation)
310+
311+
Returns:
312+
tuple of left and right channel arrays
313+
"""
314+
# Combine beat segments
269315
left_beats, right_beats, combined_duration = _combine_audio_segments(step_results)
270316

271317
# Verify combined duration (logging only)
@@ -276,6 +322,27 @@ def generate_audio_sequence_parallel(
276322
combined_duration,
277323
)
278324

325+
# Apply final processing
326+
return _apply_final_processing(left_beats, right_beats, noise_signal, noise_config)
327+
328+
329+
def _apply_final_processing(
330+
left_beats: np.ndarray,
331+
right_beats: np.ndarray,
332+
noise_signal: Optional[np.ndarray],
333+
noise_config: NoiseConfig,
334+
) -> tuple[np.ndarray, np.ndarray]:
335+
"""Apply final processing steps to the audio channels.
336+
337+
Args:
338+
left_beats: Left channel beat data
339+
right_beats: Right channel beat data
340+
noise_signal: Optional noise signal to mix
341+
noise_config: Noise configuration
342+
343+
Returns:
344+
tuple of final left and right channels
345+
"""
279346
# Mix noise if applicable
280347
if noise_signal is not None and noise_config.amplitude > 0:
281348
logger.info("Mixing '%s' noise with beat segments...", noise_config.type)
@@ -293,4 +360,4 @@ def generate_audio_sequence_parallel(
293360
left_final = left_final.astype(np.float64)
294361
right_final = right_final.astype(np.float64)
295362

296-
return left_final, right_final, total_duration # Use initial total_duration
363+
return left_final, right_final

cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"ipython",
3636
"isort",
3737
"Jirakittayakorn",
38+
"jsonschema",
3839
"Kraus",
3940
"levelname",
4041
"mccabe",

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "binaural-generator"
3-
version = "0.5.3"
3+
version = "0.6.0"
44
description = "Generate binaural beat audio with background noise for brainwave entrainment"
55
authors = [{ name = "Kayvan Sylvan", email = "kayvan@sylvan.com" }]
66
license = "MIT"
@@ -45,6 +45,9 @@ dev = [
4545
"twine>=6.1.0",
4646
]
4747

48+
[tool.setuptools.packages.find]
49+
exclude = ["audio"]
50+
4851
[tool.setuptools.package-data]
4952
binaural_generator = ["scripts/*"]
5053

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)