return to src
 
Form

Overview
This code represents a synthesizer GUI application written in Python using the PySide6 library.
The synthesizer UI supports multiple waveforms including sine, square, sawtooth, and triangle waves
with adjustable parameters attack, decay, sustain, and release (ADSR envelope), volume, pitch, and tone. 
 
Wave Generation
After defining the constants, the code generates waveforms for each key using different oscillators 
(sine, square, sawtooth, and triangle). These waveforms are stored in dictionaries for easy access.
 
MainWidget Class
The MainWidget class represents the main widget of the synthesizer application. It inherits from 
the QWidget class provided by the PySide6 library. The class contains methods for handling UI events, 
such as button presses, knob changes, and waveform selection.
 
ChatGPT was utilized to improve the docstrings in this file.

 
Modules
       
numpy
os
pygame
sounddevice

 
Classes
       
PySide6.QtWidgets.QWidget
MainWidget

 
     
Method resolution order:
MainWidget
PySide6.QtWidgets.QWidget
PySide6.QtCore.QObject
PySide6.QtGui.QPaintDevice
Shiboken.Object
builtins.object

Methods defined here:
MIDI_init(self) -> None
Initializes the MIDI functionality for the application.
 
This function sets up the MIDI thread, identifying and connecting to the appropriate
MIDI input device, and starts the thread's execution to enable the application
to asynchronously receive and process MIDI messages without obstructing the main user interface.
__init__(self) -> None
Initializes an instance of the MainWidget class, inheriting attributes and methods from QWidget.
 
Attributes:
- vol_ctrl (Volume): Manages volume control using a Volume instance.
- adsr_envelope (ADSREnvelope): Handles ADSR (Attack, Decay, Sustain, Release) envelope parameters.
- win (QWidget): Loads the UI and assigns it to the MainWidget window.
- threadpool (QThreadPool): Manages threads for concurrent operations.
- pitch_previous_value (int): Holds the default pitch value.
- pitch_shifted_keys (list[str]): Stores default key mappings for the GUI.
- octave_count (int): Defines the number of octaves for key mappings.
assign_key_handler(self, win) -> None
This function identifies all the keys within the GUI and assigns event handlers to manage
key press and release actions for each individual key. The event handlers enable the
manipulation of musical notes or actions associated with the specific keys.
Args:
win: The UI window object containing the keys for which event handlers are being assigned.
connect_knob_and_spinbox_values(self, win) -> None
Establishes connections between knob and spinbox values in the GUI.
 
Args:
win: The UI window object where knob and spinbox values are to be connected.
 
This function sets up bidirectional connections between knob values and their
corresponding spinbox values in the GUI. Changes in either the knob or spinbox
will reflect in both components to maintain synchronization.
handle_attack_knob_changed(self, value) -> None
This function handles when the attack knob value is changed.
Args:
value: the number setting for the attack knob
handle_attack_spin_box_value_changed(self) -> None
This function handles when the attack spin box is changed.
handle_decay_knob_changed(self, value) -> None
This function handles when the decay knob value is changed.
Args:
value: the number setting for the decay knob
handle_decay_spin_box_value_changed(self) -> None
This function handles when the decay spin box is changed.
handle_pitch_knob_changed(self) -> None
This function handles when the pitch knob value is changed.
handle_pitch_spin_box_value_changed(self) -> None
This function handles when the pitch spin box is changed.
handle_release_knob_changed(self, value) -> None
This function handles when the release knob value is changed.
Args:
value: the number setting for the release knob
handle_release_spin_box_value_changed(self) -> None
This function handles when the release spin box is changed.
handle_sustain_knob_changed(self, value) -> None
This function handles when the sustain knob value is changed.
Args:
value: the number setting for the sustain knob
handle_sustain_spin_box_value_changed(self) -> None
This function handles when the sustain spin box is changed.
handle_volume_knob_changed(self) -> None
This function handles when the volume knob value is changed.
handle_volume_spin_box_value_changed(self) -> None
This function handles when the volume spin box is changed.
handle_waveform_selected(self, selected_waveform) -> None
This function handles when a different waveform is
selected in the GUI and updates the selected waveform.
Args:
selected_waveform: this is the user selected waveform
key_pressed_handler(self, key) -> None
This function handles when a key is pressed.
First it maps the named keys in the GUI to the
correct notes defined by the shift in pitch.
Once the keys are mapped it updates the ADSR state
and plays the continous wave on one thread.
Args:
win: The UI window object containing the keys for which event handlers are being assigned.
key_released_handler(self) -> None
This function updates the ADSR state when a key is released
to move into the next portion of envelope.
load_ui(self) -> PySide6.QtWidgets.QWidget
Loads the graphical user interface (GUI) window and its elements from a designated UI file.
Utilizes the QUiLoader to load the UI window and its components from the specified file path.
Returns the generated GUI window after loading.
Returns:
QWidget: The generated GUI window populated with elements from the UI file.
play_loop(self, wav) -> None
This function sets up continuous play of the note.
When a key is pressed, the ADSR envelope is continuously
applied to the wave passed in. Then volume is appled and
the wave is output continously so there is no break in
the output wave.
Args:
wav: the wave sent in to be looped
set_default_values(self, win) -> None
Sets the default values for each knob on the UI:
(Attack, Decay, Sustain, Release, Volume, Pitch)
Sets the default wave selection
Args:
win: The UI window object where default values are to be set.
wave_selection(self, win) -> None
This function sets up connections between the different waveform selection buttons
on the GUI and the corresponding handler method for selecting different waveforms.
When a waveform button is clicked, the associated method for waveform selection is triggered.
Args:
win: The UI window object providing the waveform selection controls.



       DEFAULT_ATTACK = 2
DEFAULT_DECAY = 7
DEFAULT_DURATION = 0.2
DEFAULT_PITCH = 3
DEFAULT_RELEASE = 3
DEFAULT_SUSTAIN = 8
DEFAULT_VOLUME = 9
DEFAULT_VOLUME_OFFSET = 9
GUI_KEYS = ['C0', 'C#0', 'D0', 'D#0', 'E0', 'F0', 'F#0', 'G0', 'G#0', 'A0', 'A#0', 'B0', 'C1', 'C#1', 'D1', 'D#1', 'E1', 'F1', 'F#1', 'G1', ...]
MAX_AMPLITUDE = 8192
NOTE_FREQS = {'A#0': 29.14, 'A#1': 58.27, 'A#10': 29834.48, 'A#11': 59668.96, 'A#12': 119337.92, 'A#2': 116.54, 'A#3': 233.08, 'A#4': 466.16, 'A#5': 932.33, 'A#6': 1864.66, ...}
SAMPLE_RATE = 48000
__annotations__ = {'DEFAULT_ATTACK': <class 'int'>, 'DEFAULT_DECAY': <class 'int'>, 'DEFAULT_DURATION': <class 'float'>, 'DEFAULT_PITCH': <class 'int'>, 'DEFAULT_RELEASE': <class 'int'>, 'DEFAULT_SUSTAIN': <class 'int'>, 'DEFAULT_VOLUME': <class 'int'>, 'DEFAULT_VOLUME_OFFSET': <class 'int'>, 'GUI_KEYS': list[str], 'MAX_AMPLITUDE': <class 'int'>, ...}
key = 'A#12'
saw_waves = {'A#0': array([ 0, 9, 19, ..., -30, -20, -10], dtype=int16), 'A#1': array([ 0, 19, 39, ..., -64, -45, -25], dtype=int16), 'A#10': array([ 0, -6200, 3983, ..., 6714, 513, -5686], dtype=int16), 'A#11': array([ 0, 3983, 7966, ..., -2955, 1027, 5010], dtype=int16), 'A#12': array([ 0, 7966, -451, ..., 2054, -6363, 1602], dtype=int16), 'A#2': array([ 0, 39, 79, ..., -124, -85, -45], dtype=int16), 'A#3': array([ 0, 79, 159, ..., -249, -170, -90], dtype=int16), 'A#4': array([ 0, 159, 318, ..., -495, -335, -176], dtype=int16), 'A#5': array([ 0, 318, 636, ..., -957, -639, -320], dtype=int16), 'A#6': array([ 0, 636, 1272, ..., -1914, -1278, -641], dtype=int16), ...}
sine_waves = {'A#0': array([ 0, 31, 62, ..., -96, -65, -34], dtype=int16), 'A#1': array([ 0, 62, 124, ..., -204, -141, -79], dtype=int16), 'A#10': array([ 0, -5665, 8184, ..., 4398, 1602, -6714], dtype=int16), 'A#11': array([ 0, 8184, 709, ..., -7421, 3143, 7693], dtype=int16), 'A#12': array([ 0, 709, -1412, ..., 5806, -5284, 4722], dtype=int16), 'A#2': array([ 0, 124, 249, ..., -392, -267, -142], dtype=int16), 'A#3': array([ 0, 249, 499, ..., -784, -535, -285], dtype=int16), 'A#4': array([ 0, 499, 997, ..., -1545, -1052, -555], dtype=int16), 'A#5': array([ 0, 997, 1979, ..., -2940, -1987, -1005], dtype=int16), 'A#6': array([ 0, 1979, 3842, ..., -5489, -3857, -1996], dtype=int16), ...}
square_waves = {'A#0': array([ 8192, 8192, 8192, ..., -8192, -8192, -8192], dtype=int16), 'A#1': array([ 8192, 8192, 8192, ..., -8192, -8192, -8192], dtype=int16), 'A#10': array([ 8192, -8192, 8192, ..., 8192, 8192, -8192], dtype=int16), 'A#11': array([ 8192, 8192, 8192, ..., -8192, 8192, 8192], dtype=int16), 'A#12': array([ 8192, 8192, -8192, ..., 8192, -8192, 8192], dtype=int16), 'A#2': array([ 8192, 8192, 8192, ..., -8192, -8192, -8192], dtype=int16), 'A#3': array([ 8192, 8192, 8192, ..., -8192, -8192, -8192], dtype=int16), 'A#4': array([ 8192, 8192, 8192, ..., -8192, -8192, -8192], dtype=int16), 'A#5': array([ 8192, 8192, 8192, ..., -8192, -8192, -8192], dtype=int16), 'A#6': array([ 8192, 8192, 8192, ..., -8192, -8192, -8192], dtype=int16), ...}
triangle_waves = {'A#0': array([ 0, 19, 39, ..., -61, -41, -21], dtype=int16), 'A#1': array([ 0, 39, 79, ..., -129, -90, -50], dtype=int16), 'A#10': array([ 0, -3983, 7966, ..., 2955, 1027, -5010], dtype=int16), 'A#11': array([ 0, 7966, 451, ..., -5911, 2054, 6363], dtype=int16), 'A#12': array([ 0, 451, -903, ..., 4108, -3656, 3204], dtype=int16), 'A#2': array([ 0, 79, 159, ..., -249, -170, -90], dtype=int16), 'A#3': array([ 0, 159, 318, ..., -499, -340, -181], dtype=int16), 'A#4': array([ 0, 318, 636, ..., -990, -671, -353], dtype=int16), 'A#5': array([ 0, 636, 1272, ..., -1914, -1278, -641], dtype=int16), 'A#6': array([ 0, 1272, 2545, ..., -3829, -2556, -1283], dtype=int16), ...}