This commit is contained in:
Steven Dan
2025-12-11 09:43:42 +08:00
commit d8b2974133
1822 changed files with 280037 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
# Copyright 2023 XMOS LIMITED.
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
import matplotlib.pyplot as plt
import numpy as np
import soundfile
from scipy.io import wavfile # soundfile has some issues writing high Fs files
class audio_modulator:
"""
This test helper generates a wav file with a fixed sample rate and tone frequency
of a certain length.
A method then allows sections of it to be frequency modulated by a value in Hz.
The modulated signal (which uses cumultaive phase to avoid discontinuites)
may then be plotted as an FFT to understand the SNR/THD and may also be saved
as a wav file.
"""
def __init__(self, duration_s, sample_rate=48000, test_tone_hz=1000):
self.sample_rate = sample_rate
self.test_tone_hz = test_tone_hz
self.modulator = np.full(int(duration_s * sample_rate), test_tone_hz, dtype=np.float64)
def apply_frequency_deviation(self, start_s, end_s, delta_freq):
start_idx = int(start_s * self.sample_rate)
end_idx = int(end_s * self.sample_rate)
self.modulator[start_idx:end_idx] += delta_freq
def modulate_waveform(self):
# Now create the frequency modulated waveform
# this is designed to accumulate the phase so doesn't see discontinuities
# https://dsp.stackexchange.com/questions/80768/fsk-modulation-with-python
delta_phi = self.modulator * np.pi / (self.sample_rate / 2.0)
phi = np.cumsum(delta_phi)
self.waveform = np.sin(phi)
def save_modulated_wav(self, filename):
integer_output = np.int16(self.waveform * 32767)
# soundfile.write(filename, integer_output, int(self.sample_rate)) # This struggles with >768ksps
wavfile.write(filename, int(self.sample_rate), integer_output)
def plot_modulated_fft(self, filename, skip_s=None):
start_x = 0 if skip_s is None else int(skip_s * self.sample_rate) // 2 * 2
waveform = self.waveform[start_x:]
xf = np.linspace(0.0, 1.0/(2.0/self.sample_rate), waveform.size // 2)
N = xf.size
window = np.kaiser(N*2, 14)
waveform = waveform * window
yf = np.fft.fft(waveform)
fig, ax = plt.subplots()
# Plot a zoom in on the test
tone_idx = int(self.test_tone_hz / (self.sample_rate / 2) * N)
num_side_bins = 50
yf = 20 * np.log10(np.abs(yf) / N)
# ax.plot(xf[tone_idx - num_side_bins:tone_idx + num_side_bins], yf[tone_idx - num_side_bins:tone_idx + num_side_bins], marker='.')
# Plot the whole frequncy range from DC to nyquist
ax.plot(xf[:N], yf[:N], marker='.')
ax.set_xscale("log")
plt.xlim((10**1, 10**5))
plt.ylim((-200, 0))
plt.savefig(filename, dpi=150)
def load_wav(self, filename):
"""
Used for testing only - load a wav into self.waveform
"""
self.waveform, self.sample_rate = soundfile.read(filename)
if __name__ == '__main__':
"""
This module is not intended to be run directly. This is here for internal testing only.
"""
if 0:
test_len = 10
audio = audio_modulator(test_len)
for time_s in range(test_len):
modulation_hz = 10 * (time_s - (test_len) / 2)
audio.apply_frequency_deviation(time_s, time_s + 1, modulation_hz)
audio.modulate_waveform()
audio.save_modulated_wav("modulated.wav")
audio.plot_modulated_fft("modulated_fft.png")
else:
audio = audio_modulator(1)
audio.load_wav("modulated_tone_1000Hz_sd_ds.wav")
# audio = audio_modulator(1, sample_rate=3072000)
# audio.modulate_waveform()
audio.plot_modulated_fft("modulated_tone_1000Hz_sd_ds.png")
# audio.save_modulated_wav("modulated.wav")