Created
February 19, 2022 21:31
-
-
Save Splines/a5b915b1b743705242ada9e398aef4ee to your computer and use it in GitHub Desktop.
A very basic mono synthesizer using PyAudio and Matplotlib
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Installation | |
# Run `pip install pyaudio` first | |
# If that throws an error, you need to install a "wheel" first (pre-packaged binary) | |
# Follow the instructions here: https://stackoverflow.com/a/71073645/9655481 | |
# e.g. I use Python 3.9.6 and installed the file "PyAudio‑0.2.11‑cp39‑cp39‑win_amd64.whl" | |
# from here: https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyaudio | |
import time | |
import matplotlib.pyplot as plt | |
import numpy as np | |
import pyaudio | |
from matplotlib.widgets import Button, Slider | |
print('🎈 Started') | |
########################## Generate sound data ################################## | |
SAMPLE_RATE = 44100 # sample rate in hertz | |
FRAMES_PER_BUFFER = int(SAMPLE_RATE * 0.1) # corresponds to 100ms | |
init_amplitude = 0.5 # signal amplitude scaler | |
init_frequency = 600 # signal frequency in hertz | |
FRAME_DURATION = 1 / SAMPLE_RATE | |
T = FRAMES_PER_BUFFER * FRAME_DURATION | |
t = np.linspace(0, T, FRAMES_PER_BUFFER) # discrete signal time points | |
sound_data = { | |
'clipped': None, | |
'scaled': None | |
} | |
def update_sound_data(amplitude, frequency): | |
signal = amplitude * np.sin(2 * np.pi * frequency * t) | |
sound_data['clipped'] = signal.clip(-1, 1) | |
# Convert from float [-1, +1] to int16 [-32768, +32767] | |
sound_data['scaled'] = ((sound_data['clipped'] * 32767.5) - 0.5)\ | |
.astype(np.int16) | |
update_sound_data(init_amplitude, init_frequency) | |
################################ Plot ########################################## | |
fig, ax = plt.subplots() | |
line, = plt.plot(t, sound_data['clipped']) | |
ax.set_title('Synth') | |
ax.set_xlabel('Time [s]') | |
ax.set_xlim(left=0, right=10e-3) | |
ax.set_ylim(bottom=-2, top=2) | |
# Adjust the main plot to make room for the sliders | |
plt.subplots_adjust(left=0.25, bottom=0.25) | |
# Horizontal slider to control the frequency. | |
# TODO: logarithmic scale | |
ax_freq = plt.axes([0.25, 0.1, 0.65, 0.03]) | |
freq_slider = Slider( | |
ax=ax_freq, | |
label='Frequency [Hz]', | |
valmin=0.1, | |
valmax=20000, | |
valinit=init_frequency, | |
) | |
# Vertical slider to control the amplitude | |
ax_amp = plt.axes([0.1, 0.25, 0.0225, 0.63]) | |
amp_slider = Slider( | |
ax=ax_amp, | |
label="Amplitude", | |
valmin=0, | |
valmax=3, | |
valinit=init_amplitude, | |
orientation="vertical" | |
) | |
def update_plot(event): | |
update_sound_data(amp_slider.val, freq_slider.val) | |
line.set_ydata(sound_data['clipped']) | |
fig.canvas.draw_idle() | |
freq_slider.on_changed(update_plot) | |
amp_slider.on_changed(update_plot) | |
ax_reset = plt.axes([0.8, 0.025, 0.1, 0.04]) | |
button = Button(ax_reset, 'Reset', hovercolor='0.975') | |
def reset(event): | |
freq_slider.reset() | |
amp_slider.reset() | |
button.on_clicked(reset) | |
################################ PyAudio ####################################### | |
def get_data(in_data, frame_count, time_info, status): | |
# sound_data gets changed dynamically | |
out = sound_data['scaled'] | |
return (out.tobytes(), pyaudio.paContinue) | |
# PyAudio documentation: http://people.csail.mit.edu/hubert/pyaudio/ | |
p = pyaudio.PyAudio() | |
stream = p.open( | |
# int16 (number of bytes per audio frame) | |
format=p.get_format_from_width(width=2), | |
channels=1, # mono | |
rate=SAMPLE_RATE, | |
frames_per_buffer=FRAMES_PER_BUFFER, | |
output=True, | |
stream_callback=get_data) | |
stream.start_stream() | |
plt.show() | |
# while stream.is_active(): | |
# # Sleep to keep the stream active, since | |
# # the main thread must not terminate. | |
# time.sleep(0.1) | |
stream.stop_stream() | |
stream.close() | |
p.terminate() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
⚠ Right now, there's some crackling going on every time PyAudio reads from the next buffer. It even gets worse when you change the frequency. If anybody knows how to fix this, please let me know. The same problem occurs when you comment out all the lines for the Matplotlib GUI, so it's probably a PyAudio problem or rather I am doing something wrong with the buffer size.