Latest commit

History

154 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Python Polyphonic MIDI Synthesizer

Warning

  • Speaker warning
  • Experimental, WIP, math

About

Python script that simulates a basic modular synthesizer. Requires a separate MIDI input device, however a virtual one may be used on supported platforms. Generates samples based on waveforms and processes them through modules. Allows control of the modules through MIDI messages.

Package Requirements

Requires the following Python packages to be installed:

How to Use


Parts

The file parts.py contains a series of classes used to implement the functionality of a synthesizer.

Algorithms

Algorithms for use throughout ppms. Implemented in the ppms_algs class.

Oscillators

Generates waveforms based on the following types:

  • sawtooth /|
  • triangle /\
  • square |_|
  • sine ~

These are implemented in the oscillator class. You can select which waveform is generated using MIDI program change.

Patchboard

Loads modules listed in settings and stores for processing. Implemented in the patchboard class. When a note is played, the data is passed through each loaded module in order.

Synth Modules

Processes the note data. This allows implementation of effect processors. An abstract base class synthmod is used to create a new module. See the section on Modules for more information.

Mod Wheel

An abstract base class mod_control that Synth Module classes can extend to allow reading from a mod wheel on a keyboard.


Modules

Modules are used to process the waveform signal. These are loaded from the settings (see below) then the signal is passed through each in the order they were loaded. You can then set up bindings to control module parameters using MIDI controls.

Included Modules

FileDescription
mod.testFor testing MIDI control bindings.
mod.reverbAdds reverberation effect.
mod.bpassProvides a high-pass and low-pass filter.

Configuration

Settings can be found in the file settings.json. One will be created automatically the first time the script is ran.

Sample rate

You can set the sample rate here. Defaults to 44100Hz. Value is a float.

"sample_rate": 44100.0,

Keyboard events

The MIDI note on/off messages. Defaults to the following:

"sawtooth_on": 144,
"sawtooth_off": 128,
"triangle_on": 145,
"triangle_off": 129,
"square_on": 146,
"square_off": 130,
"sine_on": 147,
"sine_off": 131,

The load preset message is defined as:

"preset_msg": 192,

Impact weight

Set the impact weight. This is used for factoring keyboard velocity.

"impact_weight": 20000,

Preset directory

Folder to load preset files from.

"preset_folder": "presets",

Loading modules

Load modules to process the signal. The signal will be filtered through each module in order added.

"modules": [ "mod.test", "mod.another" ],

Loading presets

List preset files in order here. These files must be located in the folder as indicated by the preset_folder setting.

"presets": [ "example1.json", "example2.json" ],

Run with --build_presets to generate a list from the presets folder.

MIDI control bindings

Bind MIDI controls to modules or general settings.

Format: binding_name, midi_msg[0], midi_msg[1]

"bindings": [
# Default bindings
[ "master_volume", 176, 29 ],
[ "pitch_wheel", 224, 0 ],
[ "mod_wheel", 176, 1 ],
# Module bindings
# Binding names should have the format class_name.member_name
[ "test_module.set_a_value", 176, 118 ]
],

Saving data

Modules will store their setting data here on shutdown, then restore them on next run.

"module_data": []

Modules

To make a module, create a Python file in the mod folder. Define the module as a class and extend synthmod from parts.py. At minimum the process function needs to be defined. The class can then be composed as following:

  • process function - Define what happens with the signal.
def process(self, note, signal):
# Do something with the signal
return signal
  • save_data function - Return an array of binding names and the variable they are associated with.
def save_data(self):
return [
[ "example.control_a", self.value_a ],
[ "example.control_b", self.value_b ]
]

For each control in the module, create a seperate function to set its value. Then to create bindings to these controls, use the format class_name.function_name.

Example mod.test.py

from .parts import synthmod
## PPMS Synth Module for testing the patchboard.
class test_module(synthmod):
## Store test_value
__test_value = 0
## Test process, simply print the test_value.
# @param self Object pointer
# @param note The note frequency being played
# @param signal Signal data to modify
# @return Modified signal data
def process(self, note, signal):
if self.__test_value > self.MIDI_MIN:
if self.__test_value == self.MIDI_MAX:
print("Text value at max: ", self.__test_value)
else:
print("Test value: ", self.__test_value)
return signal
## Build an array of save data for the module.
# Bindings should have the format class_name.member_name.
# @param self Object pointer
# @return Module data to save
def save_data(self):
return [
[ 'test_module.set_a_value', self.__test_value ]
]
## Set test value.
# @param self Object pointer
# @param val New value to set
def set_a_value(self, val):
self.__test_value = val

Presets

Preset files are used to store module parameters and can be loaded during runtime. When the MIDI message to load a preset is received, it selects the corresponding preset file and sets the active parameters.

For setting the preset folder and MIDI message, see Configuration above.

Example preset.json

[
[ "envelope.set_attack", 100 ],
[ "envelope.set_decay", 27 ],
[ "envelope.set_sustain", 46 ],
[ "envelope.set_release", 90 ],
[ "band_pass.set_high_pass", 12 ],
[ "band_pass.set_low_pass", 42 ],
[ "reverberation.set_reverb", 38 ]
]

About

🎵 Python Polyphonic MIDI Synthesizer 🎵

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

154 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Python Polyphonic MIDI Synthesizer

Warning

  • Speaker warning
  • Experimental, WIP, math

About

Python script that simulates a basic modular synthesizer. Requires a separate MIDI input device, however a virtual one may be used on supported platforms. Generates samples based on waveforms and processes them through modules. Allows control of the modules through MIDI messages.

Package Requirements

Requires the following Python packages to be installed:

How to Use


Parts

The file parts.py contains a series of classes used to implement the functionality of a synthesizer.

Algorithms

Algorithms for use throughout ppms. Implemented in the ppms_algs class.

Oscillators

Generates waveforms based on the following types:

  • sawtooth /|
  • triangle /\
  • square |_|
  • sine ~

These are implemented in the oscillator class. You can select which waveform is generated using MIDI program change.

Patchboard

Loads modules listed in settings and stores for processing. Implemented in the patchboard class. When a note is played, the data is passed through each loaded module in order.

Synth Modules

Processes the note data. This allows implementation of effect processors. An abstract base class synthmod is used to create a new module. See the section on Modules for more information.

Mod Wheel

An abstract base class mod_control that Synth Module classes can extend to allow reading from a mod wheel on a keyboard.


Modules

Modules are used to process the waveform signal. These are loaded from the settings (see below) then the signal is passed through each in the order they were loaded. You can then set up bindings to control module parameters using MIDI controls.

Included Modules

FileDescription
mod.testFor testing MIDI control bindings.
mod.reverbAdds reverberation effect.
mod.bpassProvides a high-pass and low-pass filter.

Configuration

Settings can be found in the file settings.json. One will be created automatically the first time the script is ran.

Sample rate

You can set the sample rate here. Defaults to 44100Hz. Value is a float.

"sample_rate": 44100.0,

Keyboard events

The MIDI note on/off messages. Defaults to the following:

"sawtooth_on": 144,
"sawtooth_off": 128,
"triangle_on": 145,
"triangle_off": 129,
"square_on": 146,
"square_off": 130,
"sine_on": 147,
"sine_off": 131,

The load preset message is defined as:

"preset_msg": 192,

Impact weight

Set the impact weight. This is used for factoring keyboard velocity.

"impact_weight": 20000,

Preset directory

Folder to load preset files from.

"preset_folder": "presets",

Loading modules

Load modules to process the signal. The signal will be filtered through each module in order added.

"modules": [ "mod.test", "mod.another" ],

Loading presets

List preset files in order here. These files must be located in the folder as indicated by the preset_folder setting.

"presets": [ "example1.json", "example2.json" ],

Run with --build_presets to generate a list from the presets folder.

MIDI control bindings

Bind MIDI controls to modules or general settings.

Format: binding_name, midi_msg[0], midi_msg[1]

"bindings": [
# Default bindings
[ "master_volume", 176, 29 ],
[ "pitch_wheel", 224, 0 ],
[ "mod_wheel", 176, 1 ],
# Module bindings
# Binding names should have the format class_name.member_name
[ "test_module.set_a_value", 176, 118 ]
],

Saving data

Modules will store their setting data here on shutdown, then restore them on next run.

"module_data": []

Modules

To make a module, create a Python file in the mod folder. Define the module as a class and extend synthmod from parts.py. At minimum the process function needs to be defined. The class can then be composed as following:

  • process function - Define what happens with the signal.
def process(self, note, signal):
# Do something with the signal
return signal
  • save_data function - Return an array of binding names and the variable they are associated with.
def save_data(self):
return [
[ "example.control_a", self.value_a ],
[ "example.control_b", self.value_b ]
]

For each control in the module, create a seperate function to set its value. Then to create bindings to these controls, use the format class_name.function_name.

Example mod.test.py

from .parts import synthmod
## PPMS Synth Module for testing the patchboard.
class test_module(synthmod):
## Store test_value
__test_value = 0
## Test process, simply print the test_value.
# @param self Object pointer
# @param note The note frequency being played
# @param signal Signal data to modify
# @return Modified signal data
def process(self, note, signal):
if self.__test_value > self.MIDI_MIN:
if self.__test_value == self.MIDI_MAX:
print("Text value at max: ", self.__test_value)
else:
print("Test value: ", self.__test_value)
return signal
## Build an array of save data for the module.
# Bindings should have the format class_name.member_name.
# @param self Object pointer
# @return Module data to save
def save_data(self):
return [
[ 'test_module.set_a_value', self.__test_value ]
]
## Set test value.
# @param self Object pointer
# @param val New value to set
def set_a_value(self, val):
self.__test_value = val

Presets

Preset files are used to store module parameters and can be loaded during runtime. When the MIDI message to load a preset is received, it selects the corresponding preset file and sets the active parameters.

For setting the preset folder and MIDI message, see Configuration above.

Example preset.json

[
[ "envelope.set_attack", 100 ],
[ "envelope.set_decay", 27 ],
[ "envelope.set_sustain", 46 ],
[ "envelope.set_release", 90 ],
[ "band_pass.set_high_pass", 12 ],
[ "band_pass.set_low_pass", 42 ],
[ "reverberation.set_reverb", 38 ]
]

About

🎵 Python Polyphonic MIDI Synthesizer 🎵

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

154 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Python Polyphonic MIDI Synthesizer

Warning

  • Speaker warning
  • Experimental, WIP, math

About

Python script that simulates a basic modular synthesizer. Requires a separate MIDI input device, however a virtual one may be used on supported platforms. Generates samples based on waveforms and processes them through modules. Allows control of the modules through MIDI messages.

Package Requirements

Requires the following Python packages to be installed:

How to Use


Parts

The file parts.py contains a series of classes used to implement the functionality of a synthesizer.

Algorithms

Algorithms for use throughout ppms. Implemented in the ppms_algs class.

Oscillators

Generates waveforms based on the following types:

  • sawtooth /|
  • triangle /\
  • square |_|
  • sine ~

These are implemented in the oscillator class. You can select which waveform is generated using MIDI program change.

Patchboard

Loads modules listed in settings and stores for processing. Implemented in the patchboard class. When a note is played, the data is passed through each loaded module in order.

Synth Modules

Processes the note data. This allows implementation of effect processors. An abstract base class synthmod is used to create a new module. See the section on Modules for more information.

Mod Wheel

An abstract base class mod_control that Synth Module classes can extend to allow reading from a mod wheel on a keyboard.


Modules

Modules are used to process the waveform signal. These are loaded from the settings (see below) then the signal is passed through each in the order they were loaded. You can then set up bindings to control module parameters using MIDI controls.

Included Modules

FileDescription
mod.testFor testing MIDI control bindings.
mod.reverbAdds reverberation effect.
mod.bpassProvides a high-pass and low-pass filter.

Configuration

Settings can be found in the file settings.json. One will be created automatically the first time the script is ran.

Sample rate

You can set the sample rate here. Defaults to 44100Hz. Value is a float.

"sample_rate": 44100.0,

Keyboard events

The MIDI note on/off messages. Defaults to the following:

"sawtooth_on": 144,
"sawtooth_off": 128,
"triangle_on": 145,
"triangle_off": 129,
"square_on": 146,
"square_off": 130,
"sine_on": 147,
"sine_off": 131,

The load preset message is defined as:

"preset_msg": 192,

Impact weight

Set the impact weight. This is used for factoring keyboard velocity.

"impact_weight": 20000,

Preset directory

Folder to load preset files from.

"preset_folder": "presets",

Loading modules

Load modules to process the signal. The signal will be filtered through each module in order added.

"modules": [ "mod.test", "mod.another" ],

Loading presets

List preset files in order here. These files must be located in the folder as indicated by the preset_folder setting.

"presets": [ "example1.json", "example2.json" ],

Run with --build_presets to generate a list from the presets folder.

MIDI control bindings

Bind MIDI controls to modules or general settings.

Format: binding_name, midi_msg[0], midi_msg[1]

"bindings": [
# Default bindings
[ "master_volume", 176, 29 ],
[ "pitch_wheel", 224, 0 ],
[ "mod_wheel", 176, 1 ],
# Module bindings
# Binding names should have the format class_name.member_name
[ "test_module.set_a_value", 176, 118 ]
],

Saving data

Modules will store their setting data here on shutdown, then restore them on next run.

"module_data": []

Modules

To make a module, create a Python file in the mod folder. Define the module as a class and extend synthmod from parts.py. At minimum the process function needs to be defined. The class can then be composed as following:

  • process function - Define what happens with the signal.
def process(self, note, signal):
# Do something with the signal
return signal
  • save_data function - Return an array of binding names and the variable they are associated with.
def save_data(self):
return [
[ "example.control_a", self.value_a ],
[ "example.control_b", self.value_b ]
]

For each control in the module, create a seperate function to set its value. Then to create bindings to these controls, use the format class_name.function_name.

Example mod.test.py

from .parts import synthmod
## PPMS Synth Module for testing the patchboard.
class test_module(synthmod):
## Store test_value
__test_value = 0
## Test process, simply print the test_value.
# @param self Object pointer
# @param note The note frequency being played
# @param signal Signal data to modify
# @return Modified signal data
def process(self, note, signal):
if self.__test_value > self.MIDI_MIN:
if self.__test_value == self.MIDI_MAX:
print("Text value at max: ", self.__test_value)
else:
print("Test value: ", self.__test_value)
return signal
## Build an array of save data for the module.
# Bindings should have the format class_name.member_name.
# @param self Object pointer
# @return Module data to save
def save_data(self):
return [
[ 'test_module.set_a_value', self.__test_value ]
]
## Set test value.
# @param self Object pointer
# @param val New value to set
def set_a_value(self, val):
self.__test_value = val

Presets

Preset files are used to store module parameters and can be loaded during runtime. When the MIDI message to load a preset is received, it selects the corresponding preset file and sets the active parameters.

For setting the preset folder and MIDI message, see Configuration above.

Example preset.json

[
[ "envelope.set_attack", 100 ],
[ "envelope.set_decay", 27 ],
[ "envelope.set_sustain", 46 ],
[ "envelope.set_release", 90 ],
[ "band_pass.set_high_pass", 12 ],
[ "band_pass.set_low_pass", 42 ],
[ "reverberation.set_reverb", 38 ]
]

About

🎵 Python Polyphonic MIDI Synthesizer 🎵

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

154 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Python Polyphonic MIDI Synthesizer

Warning

  • Speaker warning
  • Experimental, WIP, math

About

Python script that simulates a basic modular synthesizer. Requires a separate MIDI input device, however a virtual one may be used on supported platforms. Generates samples based on waveforms and processes them through modules. Allows control of the modules through MIDI messages.

Package Requirements

Requires the following Python packages to be installed:

How to Use


Parts

The file parts.py contains a series of classes used to implement the functionality of a synthesizer.

Algorithms

Algorithms for use throughout ppms. Implemented in the ppms_algs class.

Oscillators

Generates waveforms based on the following types:

  • sawtooth /|
  • triangle /\
  • square |_|
  • sine ~

These are implemented in the oscillator class. You can select which waveform is generated using MIDI program change.

Patchboard

Loads modules listed in settings and stores for processing. Implemented in the patchboard class. When a note is played, the data is passed through each loaded module in order.

Synth Modules

Processes the note data. This allows implementation of effect processors. An abstract base class synthmod is used to create a new module. See the section on Modules for more information.

Mod Wheel

An abstract base class mod_control that Synth Module classes can extend to allow reading from a mod wheel on a keyboard.


Modules

Modules are used to process the waveform signal. These are loaded from the settings (see below) then the signal is passed through each in the order they were loaded. You can then set up bindings to control module parameters using MIDI controls.

Included Modules

FileDescription
mod.testFor testing MIDI control bindings.
mod.reverbAdds reverberation effect.
mod.bpassProvides a high-pass and low-pass filter.

Configuration

Settings can be found in the file settings.json. One will be created automatically the first time the script is ran.

Sample rate

You can set the sample rate here. Defaults to 44100Hz. Value is a float.

"sample_rate": 44100.0,

Keyboard events

The MIDI note on/off messages. Defaults to the following:

"sawtooth_on": 144,
"sawtooth_off": 128,
"triangle_on": 145,
"triangle_off": 129,
"square_on": 146,
"square_off": 130,
"sine_on": 147,
"sine_off": 131,

The load preset message is defined as:

"preset_msg": 192,

Impact weight

Set the impact weight. This is used for factoring keyboard velocity.

"impact_weight": 20000,

Preset directory

Folder to load preset files from.

"preset_folder": "presets",

Loading modules

Load modules to process the signal. The signal will be filtered through each module in order added.

"modules": [ "mod.test", "mod.another" ],

Loading presets

List preset files in order here. These files must be located in the folder as indicated by the preset_folder setting.

"presets": [ "example1.json", "example2.json" ],

Run with --build_presets to generate a list from the presets folder.

MIDI control bindings

Bind MIDI controls to modules or general settings.

Format: binding_name, midi_msg[0], midi_msg[1]

"bindings": [
# Default bindings
[ "master_volume", 176, 29 ],
[ "pitch_wheel", 224, 0 ],
[ "mod_wheel", 176, 1 ],
# Module bindings
# Binding names should have the format class_name.member_name
[ "test_module.set_a_value", 176, 118 ]
],

Saving data

Modules will store their setting data here on shutdown, then restore them on next run.

"module_data": []

Modules

To make a module, create a Python file in the mod folder. Define the module as a class and extend synthmod from parts.py. At minimum the process function needs to be defined. The class can then be composed as following:

  • process function - Define what happens with the signal.
def process(self, note, signal):
# Do something with the signal
return signal
  • save_data function - Return an array of binding names and the variable they are associated with.
def save_data(self):
return [
[ "example.control_a", self.value_a ],
[ "example.control_b", self.value_b ]
]

For each control in the module, create a seperate function to set its value. Then to create bindings to these controls, use the format class_name.function_name.

Example mod.test.py

from .parts import synthmod
## PPMS Synth Module for testing the patchboard.
class test_module(synthmod):
## Store test_value
__test_value = 0
## Test process, simply print the test_value.
# @param self Object pointer
# @param note The note frequency being played
# @param signal Signal data to modify
# @return Modified signal data
def process(self, note, signal):
if self.__test_value > self.MIDI_MIN:
if self.__test_value == self.MIDI_MAX:
print("Text value at max: ", self.__test_value)
else:
print("Test value: ", self.__test_value)
return signal
## Build an array of save data for the module.
# Bindings should have the format class_name.member_name.
# @param self Object pointer
# @return Module data to save
def save_data(self):
return [
[ 'test_module.set_a_value', self.__test_value ]
]
## Set test value.
# @param self Object pointer
# @param val New value to set
def set_a_value(self, val):
self.__test_value = val

Presets

Preset files are used to store module parameters and can be loaded during runtime. When the MIDI message to load a preset is received, it selects the corresponding preset file and sets the active parameters.

For setting the preset folder and MIDI message, see Configuration above.

Example preset.json

[
[ "envelope.set_attack", 100 ],
[ "envelope.set_decay", 27 ],
[ "envelope.set_sustain", 46 ],
[ "envelope.set_release", 90 ],
[ "band_pass.set_high_pass", 12 ],
[ "band_pass.set_low_pass", 42 ],
[ "reverberation.set_reverb", 38 ]
]

About

🎵 Python Polyphonic MIDI Synthesizer 🎵

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

154 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Python Polyphonic MIDI Synthesizer

Warning

  • Speaker warning
  • Experimental, WIP, math

About

Python script that simulates a basic modular synthesizer. Requires a separate MIDI input device, however a virtual one may be used on supported platforms. Generates samples based on waveforms and processes them through modules. Allows control of the modules through MIDI messages.

Package Requirements

Requires the following Python packages to be installed:

How to Use


Parts

The file parts.py contains a series of classes used to implement the functionality of a synthesizer.

Algorithms

Algorithms for use throughout ppms. Implemented in the ppms_algs class.

Oscillators

Generates waveforms based on the following types:

  • sawtooth /|
  • triangle /\
  • square |_|
  • sine ~

These are implemented in the oscillator class. You can select which waveform is generated using MIDI program change.

Patchboard

Loads modules listed in settings and stores for processing. Implemented in the patchboard class. When a note is played, the data is passed through each loaded module in order.

Synth Modules

Processes the note data. This allows implementation of effect processors. An abstract base class synthmod is used to create a new module. See the section on Modules for more information.

Mod Wheel

An abstract base class mod_control that Synth Module classes can extend to allow reading from a mod wheel on a keyboard.


Modules

Modules are used to process the waveform signal. These are loaded from the settings (see below) then the signal is passed through each in the order they were loaded. You can then set up bindings to control module parameters using MIDI controls.

Included Modules

FileDescription
mod.testFor testing MIDI control bindings.
mod.reverbAdds reverberation effect.
mod.bpassProvides a high-pass and low-pass filter.

Configuration

Settings can be found in the file settings.json. One will be created automatically the first time the script is ran.

Sample rate

You can set the sample rate here. Defaults to 44100Hz. Value is a float.

"sample_rate": 44100.0,

Keyboard events

The MIDI note on/off messages. Defaults to the following:

"sawtooth_on": 144,
"sawtooth_off": 128,
"triangle_on": 145,
"triangle_off": 129,
"square_on": 146,
"square_off": 130,
"sine_on": 147,
"sine_off": 131,

The load preset message is defined as:

"preset_msg": 192,

Impact weight

Set the impact weight. This is used for factoring keyboard velocity.

"impact_weight": 20000,

Preset directory

Folder to load preset files from.

"preset_folder": "presets",

Loading modules

Load modules to process the signal. The signal will be filtered through each module in order added.

"modules": [ "mod.test", "mod.another" ],

Loading presets

List preset files in order here. These files must be located in the folder as indicated by the preset_folder setting.

"presets": [ "example1.json", "example2.json" ],

Run with --build_presets to generate a list from the presets folder.

MIDI control bindings

Bind MIDI controls to modules or general settings.

Format: binding_name, midi_msg[0], midi_msg[1]

"bindings": [
# Default bindings
[ "master_volume", 176, 29 ],
[ "pitch_wheel", 224, 0 ],
[ "mod_wheel", 176, 1 ],
# Module bindings
# Binding names should have the format class_name.member_name
[ "test_module.set_a_value", 176, 118 ]
],

Saving data

Modules will store their setting data here on shutdown, then restore them on next run.

"module_data": []

Modules

To make a module, create a Python file in the mod folder. Define the module as a class and extend synthmod from parts.py. At minimum the process function needs to be defined. The class can then be composed as following:

  • process function - Define what happens with the signal.
def process(self, note, signal):
# Do something with the signal
return signal
  • save_data function - Return an array of binding names and the variable they are associated with.
def save_data(self):
return [
[ "example.control_a", self.value_a ],
[ "example.control_b", self.value_b ]
]

For each control in the module, create a seperate function to set its value. Then to create bindings to these controls, use the format class_name.function_name.

Example mod.test.py

from .parts import synthmod
## PPMS Synth Module for testing the patchboard.
class test_module(synthmod):
## Store test_value
__test_value = 0
## Test process, simply print the test_value.
# @param self Object pointer
# @param note The note frequency being played
# @param signal Signal data to modify
# @return Modified signal data
def process(self, note, signal):
if self.__test_value > self.MIDI_MIN:
if self.__test_value == self.MIDI_MAX:
print("Text value at max: ", self.__test_value)
else:
print("Test value: ", self.__test_value)
return signal
## Build an array of save data for the module.
# Bindings should have the format class_name.member_name.
# @param self Object pointer
# @return Module data to save
def save_data(self):
return [
[ 'test_module.set_a_value', self.__test_value ]
]
## Set test value.
# @param self Object pointer
# @param val New value to set
def set_a_value(self, val):
self.__test_value = val

Presets

Preset files are used to store module parameters and can be loaded during runtime. When the MIDI message to load a preset is received, it selects the corresponding preset file and sets the active parameters.

For setting the preset folder and MIDI message, see Configuration above.

Example preset.json

[
[ "envelope.set_attack", 100 ],
[ "envelope.set_decay", 27 ],
[ "envelope.set_sustain", 46 ],
[ "envelope.set_release", 90 ],
[ "band_pass.set_high_pass", 12 ],
[ "band_pass.set_low_pass", 42 ],
[ "reverberation.set_reverb", 38 ]
]

About

🎵 Python Polyphonic MIDI Synthesizer 🎵

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

154 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Python Polyphonic MIDI Synthesizer

Warning

  • Speaker warning
  • Experimental, WIP, math

About

Python script that simulates a basic modular synthesizer. Requires a separate MIDI input device, however a virtual one may be used on supported platforms. Generates samples based on waveforms and processes them through modules. Allows control of the modules through MIDI messages.

Package Requirements

Requires the following Python packages to be installed:

How to Use


Parts

The file parts.py contains a series of classes used to implement the functionality of a synthesizer.

Algorithms

Algorithms for use throughout ppms. Implemented in the ppms_algs class.

Oscillators

Generates waveforms based on the following types:

  • sawtooth /|
  • triangle /\
  • square |_|
  • sine ~

These are implemented in the oscillator class. You can select which waveform is generated using MIDI program change.

Patchboard

Loads modules listed in settings and stores for processing. Implemented in the patchboard class. When a note is played, the data is passed through each loaded module in order.

Synth Modules

Processes the note data. This allows implementation of effect processors. An abstract base class synthmod is used to create a new module. See the section on Modules for more information.

Mod Wheel

An abstract base class mod_control that Synth Module classes can extend to allow reading from a mod wheel on a keyboard.


Modules

Modules are used to process the waveform signal. These are loaded from the settings (see below) then the signal is passed through each in the order they were loaded. You can then set up bindings to control module parameters using MIDI controls.

Included Modules

FileDescription
mod.testFor testing MIDI control bindings.
mod.reverbAdds reverberation effect.
mod.bpassProvides a high-pass and low-pass filter.

Configuration

Settings can be found in the file settings.json. One will be created automatically the first time the script is ran.

Sample rate

You can set the sample rate here. Defaults to 44100Hz. Value is a float.

"sample_rate": 44100.0,

Keyboard events

The MIDI note on/off messages. Defaults to the following:

"sawtooth_on": 144,
"sawtooth_off": 128,
"triangle_on": 145,
"triangle_off": 129,
"square_on": 146,
"square_off": 130,
"sine_on": 147,
"sine_off": 131,

The load preset message is defined as:

"preset_msg": 192,

Impact weight

Set the impact weight. This is used for factoring keyboard velocity.

"impact_weight": 20000,

Preset directory

Folder to load preset files from.

"preset_folder": "presets",

Loading modules

Load modules to process the signal. The signal will be filtered through each module in order added.

"modules": [ "mod.test", "mod.another" ],

Loading presets

List preset files in order here. These files must be located in the folder as indicated by the preset_folder setting.

"presets": [ "example1.json", "example2.json" ],

Run with --build_presets to generate a list from the presets folder.

MIDI control bindings

Bind MIDI controls to modules or general settings.

Format: binding_name, midi_msg[0], midi_msg[1]

"bindings": [
# Default bindings
[ "master_volume", 176, 29 ],
[ "pitch_wheel", 224, 0 ],
[ "mod_wheel", 176, 1 ],
# Module bindings
# Binding names should have the format class_name.member_name
[ "test_module.set_a_value", 176, 118 ]
],

Saving data

Modules will store their setting data here on shutdown, then restore them on next run.

"module_data": []

Modules

To make a module, create a Python file in the mod folder. Define the module as a class and extend synthmod from parts.py. At minimum the process function needs to be defined. The class can then be composed as following:

  • process function - Define what happens with the signal.
def process(self, note, signal):
# Do something with the signal
return signal
  • save_data function - Return an array of binding names and the variable they are associated with.
def save_data(self):
return [
[ "example.control_a", self.value_a ],
[ "example.control_b", self.value_b ]
]

For each control in the module, create a seperate function to set its value. Then to create bindings to these controls, use the format class_name.function_name.

Example mod.test.py

from .parts import synthmod
## PPMS Synth Module for testing the patchboard.
class test_module(synthmod):
## Store test_value
__test_value = 0
## Test process, simply print the test_value.
# @param self Object pointer
# @param note The note frequency being played
# @param signal Signal data to modify
# @return Modified signal data
def process(self, note, signal):
if self.__test_value > self.MIDI_MIN:
if self.__test_value == self.MIDI_MAX:
print("Text value at max: ", self.__test_value)
else:
print("Test value: ", self.__test_value)
return signal
## Build an array of save data for the module.
# Bindings should have the format class_name.member_name.
# @param self Object pointer
# @return Module data to save
def save_data(self):
return [
[ 'test_module.set_a_value', self.__test_value ]
]
## Set test value.
# @param self Object pointer
# @param val New value to set
def set_a_value(self, val):
self.__test_value = val

Presets

Preset files are used to store module parameters and can be loaded during runtime. When the MIDI message to load a preset is received, it selects the corresponding preset file and sets the active parameters.

For setting the preset folder and MIDI message, see Configuration above.

Example preset.json

[
[ "envelope.set_attack", 100 ],
[ "envelope.set_decay", 27 ],
[ "envelope.set_sustain", 46 ],
[ "envelope.set_release", 90 ],
[ "band_pass.set_high_pass", 12 ],
[ "band_pass.set_low_pass", 42 ],
[ "reverberation.set_reverb", 38 ]
]

About

🎵 Python Polyphonic MIDI Synthesizer 🎵

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

154 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Python Polyphonic MIDI Synthesizer

Warning

  • Speaker warning
  • Experimental, WIP, math

About

Python script that simulates a basic modular synthesizer. Requires a separate MIDI input device, however a virtual one may be used on supported platforms. Generates samples based on waveforms and processes them through modules. Allows control of the modules through MIDI messages.

Package Requirements

Requires the following Python packages to be installed:

How to Use


Parts

The file parts.py contains a series of classes used to implement the functionality of a synthesizer.

Algorithms

Algorithms for use throughout ppms. Implemented in the ppms_algs class.

Oscillators

Generates waveforms based on the following types:

  • sawtooth /|
  • triangle /\
  • square |_|
  • sine ~

These are implemented in the oscillator class. You can select which waveform is generated using MIDI program change.

Patchboard

Loads modules listed in settings and stores for processing. Implemented in the patchboard class. When a note is played, the data is passed through each loaded module in order.

Synth Modules

Processes the note data. This allows implementation of effect processors. An abstract base class synthmod is used to create a new module. See the section on Modules for more information.

Mod Wheel

An abstract base class mod_control that Synth Module classes can extend to allow reading from a mod wheel on a keyboard.


Modules

Modules are used to process the waveform signal. These are loaded from the settings (see below) then the signal is passed through each in the order they were loaded. You can then set up bindings to control module parameters using MIDI controls.

Included Modules

FileDescription
mod.testFor testing MIDI control bindings.
mod.reverbAdds reverberation effect.
mod.bpassProvides a high-pass and low-pass filter.

Configuration

Settings can be found in the file settings.json. One will be created automatically the first time the script is ran.

Sample rate

You can set the sample rate here. Defaults to 44100Hz. Value is a float.

"sample_rate": 44100.0,

Keyboard events

The MIDI note on/off messages. Defaults to the following:

"sawtooth_on": 144,
"sawtooth_off": 128,
"triangle_on": 145,
"triangle_off": 129,
"square_on": 146,
"square_off": 130,
"sine_on": 147,
"sine_off": 131,

The load preset message is defined as:

"preset_msg": 192,

Impact weight

Set the impact weight. This is used for factoring keyboard velocity.

"impact_weight": 20000,

Preset directory

Folder to load preset files from.

"preset_folder": "presets",

Loading modules

Load modules to process the signal. The signal will be filtered through each module in order added.

"modules": [ "mod.test", "mod.another" ],

Loading presets

List preset files in order here. These files must be located in the folder as indicated by the preset_folder setting.

"presets": [ "example1.json", "example2.json" ],

Run with --build_presets to generate a list from the presets folder.

MIDI control bindings

Bind MIDI controls to modules or general settings.

Format: binding_name, midi_msg[0], midi_msg[1]

"bindings": [
# Default bindings
[ "master_volume", 176, 29 ],
[ "pitch_wheel", 224, 0 ],
[ "mod_wheel", 176, 1 ],
# Module bindings
# Binding names should have the format class_name.member_name
[ "test_module.set_a_value", 176, 118 ]
],

Saving data

Modules will store their setting data here on shutdown, then restore them on next run.

"module_data": []

Modules

To make a module, create a Python file in the mod folder. Define the module as a class and extend synthmod from parts.py. At minimum the process function needs to be defined. The class can then be composed as following:

  • process function - Define what happens with the signal.
def process(self, note, signal):
# Do something with the signal
return signal
  • save_data function - Return an array of binding names and the variable they are associated with.
def save_data(self):
return [
[ "example.control_a", self.value_a ],
[ "example.control_b", self.value_b ]
]

For each control in the module, create a seperate function to set its value. Then to create bindings to these controls, use the format class_name.function_name.

Example mod.test.py

from .parts import synthmod
## PPMS Synth Module for testing the patchboard.
class test_module(synthmod):
## Store test_value
__test_value = 0
## Test process, simply print the test_value.
# @param self Object pointer
# @param note The note frequency being played
# @param signal Signal data to modify
# @return Modified signal data
def process(self, note, signal):
if self.__test_value > self.MIDI_MIN:
if self.__test_value == self.MIDI_MAX:
print("Text value at max: ", self.__test_value)
else:
print("Test value: ", self.__test_value)
return signal
## Build an array of save data for the module.
# Bindings should have the format class_name.member_name.
# @param self Object pointer
# @return Module data to save
def save_data(self):
return [
[ 'test_module.set_a_value', self.__test_value ]
]
## Set test value.
# @param self Object pointer
# @param val New value to set
def set_a_value(self, val):
self.__test_value = val

Presets

Preset files are used to store module parameters and can be loaded during runtime. When the MIDI message to load a preset is received, it selects the corresponding preset file and sets the active parameters.

For setting the preset folder and MIDI message, see Configuration above.

Example preset.json

[
[ "envelope.set_attack", 100 ],
[ "envelope.set_decay", 27 ],
[ "envelope.set_sustain", 46 ],
[ "envelope.set_release", 90 ],
[ "band_pass.set_high_pass", 12 ],
[ "band_pass.set_low_pass", 42 ],
[ "reverberation.set_reverb", 38 ]
]

About

🎵 Python Polyphonic MIDI Synthesizer 🎵

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

154 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Python Polyphonic MIDI Synthesizer

Warning

  • Speaker warning
  • Experimental, WIP, math

About

Python script that simulates a basic modular synthesizer. Requires a separate MIDI input device, however a virtual one may be used on supported platforms. Generates samples based on waveforms and processes them through modules. Allows control of the modules through MIDI messages.

Package Requirements

Requires the following Python packages to be installed:

How to Use


Parts

The file parts.py contains a series of classes used to implement the functionality of a synthesizer.

Algorithms

Algorithms for use throughout ppms. Implemented in the ppms_algs class.

Oscillators

Generates waveforms based on the following types:

  • sawtooth /|
  • triangle /\
  • square |_|
  • sine ~

These are implemented in the oscillator class. You can select which waveform is generated using MIDI program change.

Patchboard

Loads modules listed in settings and stores for processing. Implemented in the patchboard class. When a note is played, the data is passed through each loaded module in order.

Synth Modules

Processes the note data. This allows implementation of effect processors. An abstract base class synthmod is used to create a new module. See the section on Modules for more information.

Mod Wheel

An abstract base class mod_control that Synth Module classes can extend to allow reading from a mod wheel on a keyboard.


Modules

Modules are used to process the waveform signal. These are loaded from the settings (see below) then the signal is passed through each in the order they were loaded. You can then set up bindings to control module parameters using MIDI controls.

Included Modules

FileDescription
mod.testFor testing MIDI control bindings.
mod.reverbAdds reverberation effect.
mod.bpassProvides a high-pass and low-pass filter.

Configuration

Settings can be found in the file settings.json. One will be created automatically the first time the script is ran.

Sample rate

You can set the sample rate here. Defaults to 44100Hz. Value is a float.

"sample_rate": 44100.0,

Keyboard events

The MIDI note on/off messages. Defaults to the following:

"sawtooth_on": 144,
"sawtooth_off": 128,
"triangle_on": 145,
"triangle_off": 129,
"square_on": 146,
"square_off": 130,
"sine_on": 147,
"sine_off": 131,

The load preset message is defined as:

"preset_msg": 192,

Impact weight

Set the impact weight. This is used for factoring keyboard velocity.

"impact_weight": 20000,

Preset directory

Folder to load preset files from.

"preset_folder": "presets",

Loading modules

Load modules to process the signal. The signal will be filtered through each module in order added.

"modules": [ "mod.test", "mod.another" ],

Loading presets

List preset files in order here. These files must be located in the folder as indicated by the preset_folder setting.

"presets": [ "example1.json", "example2.json" ],

Run with --build_presets to generate a list from the presets folder.

MIDI control bindings

Bind MIDI controls to modules or general settings.

Format: binding_name, midi_msg[0], midi_msg[1]

"bindings": [
# Default bindings
[ "master_volume", 176, 29 ],
[ "pitch_wheel", 224, 0 ],
[ "mod_wheel", 176, 1 ],
# Module bindings
# Binding names should have the format class_name.member_name
[ "test_module.set_a_value", 176, 118 ]
],

Saving data

Modules will store their setting data here on shutdown, then restore them on next run.

"module_data": []

Modules

To make a module, create a Python file in the mod folder. Define the module as a class and extend synthmod from parts.py. At minimum the process function needs to be defined. The class can then be composed as following:

  • process function - Define what happens with the signal.
def process(self, note, signal):
# Do something with the signal
return signal
  • save_data function - Return an array of binding names and the variable they are associated with.
def save_data(self):
return [
[ "example.control_a", self.value_a ],
[ "example.control_b", self.value_b ]
]

For each control in the module, create a seperate function to set its value. Then to create bindings to these controls, use the format class_name.function_name.

Example mod.test.py

from .parts import synthmod
## PPMS Synth Module for testing the patchboard.
class test_module(synthmod):
## Store test_value
__test_value = 0
## Test process, simply print the test_value.
# @param self Object pointer
# @param note The note frequency being played
# @param signal Signal data to modify
# @return Modified signal data
def process(self, note, signal):
if self.__test_value > self.MIDI_MIN:
if self.__test_value == self.MIDI_MAX:
print("Text value at max: ", self.__test_value)
else:
print("Test value: ", self.__test_value)
return signal
## Build an array of save data for the module.
# Bindings should have the format class_name.member_name.
# @param self Object pointer
# @return Module data to save
def save_data(self):
return [
[ 'test_module.set_a_value', self.__test_value ]
]
## Set test value.
# @param self Object pointer
# @param val New value to set
def set_a_value(self, val):
self.__test_value = val

Presets

Preset files are used to store module parameters and can be loaded during runtime. When the MIDI message to load a preset is received, it selects the corresponding preset file and sets the active parameters.

For setting the preset folder and MIDI message, see Configuration above.

Example preset.json

[
[ "envelope.set_attack", 100 ],
[ "envelope.set_decay", 27 ],
[ "envelope.set_sustain", 46 ],
[ "envelope.set_release", 90 ],
[ "band_pass.set_high_pass", 12 ],
[ "band_pass.set_low_pass", 42 ],
[ "reverberation.set_reverb", 38 ]
]

About

🎵 Python Polyphonic MIDI Synthesizer 🎵

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Contributors

Languages