Project Analysis // Python / DSP
RESONANCE: Генератор бинауральных ритмов и нейро-частот на Python (DSP & Sounddevice)
RESONANCE: Архитектура генератора бинауральных ритмов и цифровая обработка сигналов на Python
Генерация звука и цифровая обработка сигналов (DSP) в реальном времени — одна из сложнейших задач в Python-разработке. Приложение RESONANCE создавалось как высокоточный инженерный инструмент: автономный генератор частоты звука и бинауральных ритмов для направленной нейро-стимуляции (Brain Entrainment) с аппаратной разгрузкой GUI и сохранением в студийный 16-bit WAV формат.
В этом материале разобран полный цикл: от математической модели сложения фаз и фильтрации сетевых наводок до обхода ограничений Python GIL при работе с
sounddevice и CustomTkinter.---
1. Что такое бинауральные ритмы и физика нейро-частот
Физический принцип: Если подать в левое ухо синусоидальный тон с частотой F₁ = 200 Гц, а в правое — F₂ = 210 Гц, мозг человека не слышит два изолированных тона. В верхнем оливарном комплексе ствола мозга происходит фазовое сложение, и формируется иллюзорный бинауральный ритм разностной частоты:
Δf = |F₂ − F₁| = 10 Гц
Δf = |F₂ − F₁| = 10 Гц
Эта разностная частота соответствует естественным ритмам человеческого мозга, регистрируемым на ЭЭГ:
Основная инженерная цель проекта RESONANCE — синтезировать эти колебания с абсолютной чистотой, без гармонических искажений дешевых ЦАП и с защитой от наводок электросети.
---
2. Архитектура системы: Разделение потоков и обход Python GIL
Главная проблема при синтезе аудио на Python — это блокировка графического интерфейса. Если генерация массива сэмплов и отправка в драйвер звуковой карты происходят в главном потоке, интерфейс намертво зависает, а в наушниках раздаются треск и щелчки (buffer underrun).
source.text
utf-8
[ GUI Layer: CustomTkinter ]
│ (User Controls: Carrier, Beat, Volume, Preset)
▼
[ Controller: main.py ]
│
┌────┴──────────────────────────┐
▼ ▼
[ Non-blocking Live Stream ] [ Export Pipeline: WAV ]
AudioEngine (C-thread Callback) NumPy Vectorization (Float -> Int16)
sounddevice.OutputStream scipy.io.wavfile (16-bit PCM)
Критический архитектурный сбой и его фикс:
Стандартный вызов
Драйвер операционной системы сам запрашивает чанки фиксированного размера (
Стандартный вызов
sounddevice.play() является синхронно-блокирующим. Решение заключалось в переходе на потоковый C-коллбэк: sd.OutputStream(channels=2, callback=callback, samplerate=44100). Драйвер операционной системы сам запрашивает чанки фиксированного размера (
frames=1024) в отдельном системном потоке, высвобождая Python GIL для плавной анимации интерфейса со скоростью 60 FPS.---
3. Математика синтеза звука: Стерео-фазирование и цветной шум
Ядро генератора
audio_engine.py вычисляет сэмплы на основе векторизованных операций библиотеки NumPy:
source.python
utf-8
def generate_binaural(self, carrier, beat, duration, volume=0.5, noise_type='none', noise_vol=0.1, enki_mode=False):
# Генерация массива меток времени для дискретизации 44.1 kHz
t = np.linspace(0, duration, int(self.sample_rate * duration))
# Расчет несущих частот для левого и правого канала
f_left = carrier - (beat / 2.0)
f_right = carrier + (beat / 2.0)
# Генерация чистых синусоид
left_wave = np.sin(2 * np.pi * f_left * t) * volume
right_wave = np.sin(2 * np.pi * f_right * t) * volume
# Подмешивание фонового шума при необходимости
if noise_type != 'none' and noise_vol > 0:
n = self.generate_noise(noise_type, len(t), noise_vol)
left_wave += n
right_wave += n
# Объединение в стереоматрицу (samples, 2)
audio = np.vstack((left_wave, right_wave)).T
# Фильтрация сетевого фона 50 Гц
if enki_mode:
audio = self.apply_enlil_filter(audio)
return np.clip(audio, -1.0, 1.0)
4. Генерация коричневого (Brownian) шума
Для комфортного засыпания и глубокой концентрации в проект был встроен генератор цветного шума. Коричневый шум получается путем интегрирования (кумулятивной суммы) белого шума, что дает спад мощности 6 дБ на октаву:
source.python
utf-8
def generate_noise(self, n_type, frames, volume):
if n_type == 'none' or volume <= 0:
return np.zeros(frames)
noise = np.random.uniform(-1, 1, frames)
if n_type == 'brown':
noise = np.cumsum(noise) # Интегрирование во временной области
std_val = np.std(noise)
if std_val != 0:
noise = (noise / std_val) * 0.2
return noise * volume
---
5. Режекторный фильтр 50 Гц: Enlil Filter (SciPy iirnotch)
При генерации звука низкой частоты (например, несущая 100 Гц или 150 Гц) паразитный сетевой фон переменного тока (50 Гц в электросети) создает разрушительные гармоники.
Для их бесследного удаления был реализован режим Enlil Notch Filter на базе функции
scipy.signal.iirnotch.
[+]
Математическая модель и исходный код фильтра Enlil
▼
Дискретная передаточная характеристика фильтра в Z-области:
source.text
utf-8
H(z) = [ 1 − 2·cos(ω₀)·z⁻¹ + z⁻² ] / [ 1 − 2·r·cos(ω₀)·z⁻¹ + r²·z⁻² ]
Код реализации на Python:
source.python
utf-8
from scipy.signal import iirnotch, lfilter
def apply_enlil_filter(self, data):
"""
Узкополосный режекторный IIR-фильтр для вырезания наводки 50 Гц.
f0 = 50.0 Гц — центральная частота подавления
Q = 30.0 — фактор добротности (высокая селективность без искажения полезного спектра)
"""
f0 = 50.0
Q = 30.0
b, a = iirnotch(f0, Q, self.sample_rate)
return lfilter(b, a, data, axis=0)
---
6. Экспорт в WAV 16-bit PCM без переполнения диапазона
Стриминговый движок генерирует аудио во
float32 в диапазоне от -1.0 до +1.0. Для сохранения в несжатый аудио-формат, воспроизводимый на любых аудиоплеерах и смартфонах, данные конвертируются в целочисленный 16-битный PCM (int16):
source.python
utf-8
def save_to_wav(self, filename, data):
# Масштабирование в диапазон [-32767, 32767]
audio_int16 = (data * 32767).astype(np.int16)
wavfile.write(filename, self.sample_rate, audio_int16)
---