The AwesomeWaveSplineMachine is a software synthesizer that utilizes what I call dynamic WaveSpline synthesis. For the nerds, this is basically the interpretation of a set of temporal-ordered unitless time-value pairs as parametrically exponentially interpolated one-dimensional spline that you can manipulate in real-time while playback.
I wonder if nobody came up with something like this, but I couldn't find anything directly related. I'm curious, let me know if you stumbled upon similar projects.
A screenshot of the AwesomeWaveSplineMachine in action. You can try it yourself, check the link below.
This is not a production-ready music instrument! Some key features are missing, likely there are some bugs, and you can expect audio clicks and glitches under certain conditions. It's a prototype, a proof of concept and my personal playground. But yes, you can make music with it.
The AwesomeWaveSplineMachine runs in your Browser. Check out the link below.
The software is only tested on MacOS with Chrome and Firefox. In theory, it's cross platform. It probably won't work with outdated or esoteric browsers.
It's designed to support touch interaction and should work on some mobile devices. At least I could run it on some Android devices. I haven't had the time to test this extensively, so expect some issues. I recommend using a desktop or laptop.
Caution: You can scrap your speakers with this tool. You can literally melt the coil. Use it at your own risk! Make sure that Protection is activated and that the Wave Generator waveform always crosses zero within a reasonable amount of time.
Click the link below to try the AWSM synthesizer in your browser.
🚀 AWSM - AwesomeWaveSplineMachine
For more information consider one of the following links.
đź“š User Guide
🏗 Developer Guide
🏛 License
Have fun, make noise, let me know what you think.
Regarding questions and suggestions write an email to rnd7[at]riseup.net, I actually appreciate feedback.
The following is an overview of how to use the AwesomeWaveSplineMachine. For those who don't want to read this. Learning by doing should also work.
All the sections are listed top to bottom in the table of contents. Use the links to jump to a section.
Link | Description |
---|---|
Concept | What I thought and how it works |
User Interface | Overview of the user interface |
Header section | The header section provides access to global parameters and functions. |
WaveSpline section | In the WaveSpline section you can edit the waveform of the selected Generator . Click and drag the WaveSplineNodes around, longpress to add a new one. Use the list view on the right to remove a node. |
Generator section | A Generator is someting that outputs an signal, it does not have to be audible. The generator section shows the parameters of the selected Generator you can configure how the waveform is interpreted and played back. |
Voice section | Everything that can be played is a Voice . The voice section lets you configure all aspects of the selected Voice and lets you toggle between the Generators . Each Voice might utilize up to three Generators . Wave, Pitch and Gain. |
Voices section | Use the voices section to toggle between Voices . You will find all voices that are currently played back in this section, and you might click kill to stop them. |
Keyboard section | Use the keyboard to spawn new voices or to change the pitch of the active Voice . |
Mobile Devices | In order to support small screen sizes the UI is responsive. The layout changes and some settings are relocated. |
No sines, squares, triangles or sawtooths. I always liked the idea of manipulating the waveform directly. As mentioned this is not my first approach to this idea. I know it takes more than a fancy oscillator to make a synthesizer fun to play with, but that was the starting point.
I was thinking of something to create microtonal soundscapes, sustained clusters and evil bass sounds. That seems to be a suitable field of application for this form of synthesis.
Expect AWSM to work like a polyphonic drone synth. Therefore, although that is configurable, voices are sustained per default. You have to actively kill them.
Every single note that is played can be controlled individually. A Voice is accessible throughout its entire life cycle. The waveform and parameters of each Voice are independently configurable. The principle of having a discrete parametric preset as template for a instrument or a sound is softened. Voices float and evolve.
There is no filter section. For a similar effect you might want to try fiddling with the exponent settings or try moving around the WaveSplineNodes.
Time, amplitude, frequency, duration, wavelength, tempo, note value, measures, all the musical units are the same. That's not true, but these are tightly coupled. To account for this enlightenment WaveSplines are unitless and universal, might be quantized and are optionally bound to a master tempo. There is no distinction between VCO or LFO. The frequency range is just prevented from being zero and exceeding the upper end of the audible spectrum.
I like the principle of rhythmic sequencing without notes or steps. Similar to some modular synths, simple sequences can be created using a gain WaveSpline. Each voice can alsp be melodically sequenced using a seperate pitch WaveSpline, roughly comparable to what an arpeggiator does.
While I understand the principle of the octave as somehow universal, the common subdivisions seem to be a wise choice, but not without alternatives. Why not try to divide it by five. The virtual keyboard can be configured accordingly. I also already mentioned mircotonality,
This is a screenshot of default state of the user interface. Basically what you see when you start the application. Naming is random and will vary. The application is quite consistent regarding its user interface, so what you see is almost everything you have to deal with. The user interface elements are grouped into different sections. Except for the header and the keyboard these are labeled on the top left corner. You will find the corresponding section in the table of content.
You find some global settings in the header section.
Within the WaveSpline View
section you choose a Time unit
. All units other than Frequency
are bound to the global Tempo
value.
Global mix output volume value. All Wave Generator outputs are mixed and multiplied with the Main Out
value.
If active the audio output is muted if no zero crossing occurs within a period of 10 seconds. The indicator will start to blink in this case. As soon as a the audio signal crosses zero again the output is continued. I strongly suggest not disabling this feature, but you can by clicking the toggle. The image shows the toggle in its active state.
Releases all voices. Release
values are still taken into account.
Within this section you can modify the waveforms by dragging the WaveSplineNodes
around on the canvas or using the rotary controllers in the WaveSplineNode
list.
Click
to select the closest WaveSplineNode
on the canvas. Drag nodes to modify the waveform. Use Longpress
to add new nodes at the pointer position. The canvas shows always the WaveSpline
of the currently selected Generator
within the active Voice
.
The currently selected WaveSplineNode
is highlighted.
Removal of a WaveSplineNode
is currently only possible by clicking on the remove button in the list view.
Just a different view of the WaveSplineNodes
shown on the canvas. Use this to manually input WaveSplineNode
values. The entries are sorted according to the position on the canvas.
The currently selected WaveSplineNode
is highlighted.
There are two types of WaveSplineNodes
depending on the type chosen in the WaveSpline Settings section.
If the type of the WaveSpline
is Spline
or Step
.
When the WaveSpline
type is Nodes
.
The temporal position of the WaveSplineNode
within the waveform. The same as dragging a node from left to right on the canvas.
The value of the WaveSplineNode
within the waveform at the given time. This can either be the amplitude or the pitch and is the same as dragging a node up or down.
Only applies for WaveSplines
of type Nodes
. Use it to choose the righthand exponent individually per WaveSplineNode
. Choose a value of 1 for linear interpolation.
Delete a WaveSplineNode
.
Configure how the WaveSpline
is interpreted. Always shows the values of the selected Generator
within the active Voice
Change WaveSpline
specific values. Depending on the selected WaveSpline
type the view differs.
Generator Settings for WaveSpline
of type Spline
WaveSpline Settings for WaveSpline
of type Nodes
or Step
.
Click
to open a modal. Choose between three different types of WaveSpline. Spline
uses exponential interpolation. All nodes share the same exponent value. Nodes
is basically the same as Spline
but with individual exponent settings per Node. Step
does not interpolate between the nodes, useful to create pitch sequences for example, not particular suitable for audio output.
The modal that opens up next to the button will look like this. The currently selected type is highlighted.
Only applicable for WaveSplines
of type Spline
. Sets the exponent used by all nodes of the WaveSpline
. A value of 1 equals linear Interpolation, a value around 2 is similar to a sine wave. Values lower than 1 produce spikes, higher values tend towards a square wave.
Shift the phase of the WaveSpline
. Useful to carefully align the waveforms of multiple voices.
WaveSplines
are unitless, so the view determines the way it is rendered as audio, pitch or gain signal.
The view when Time Grid value is zero which equals infinity, which means no raster.
When using a non zero Time Grid value.
The Time Unit changes the input method of the Timing control. You can choose between Frequency
, Common
, Note
and Measure
depending on your preferences. Note that only Frequency
is independent from the global Tempo
, while all other options are bound to it and will be scaled when changing the value.
The modal that pops up lets you select the desired value. The current selection is highlighted.
Depending on the selected Time Unit the control changes to allow the manual input of a frequency respectively a note values. The keyboard section changes the frequency value of the Wave
generator within the active Voice
.
This is the input if the selected Time Unit is Frequency
. Select a frequency. This is the only value that is not bound to the global Tempo
.
This is the input if the selected Time Unit is Common
. Choose from common musical rhytm values. Bound to global Tempo
.
This is the input if the selected Time Unit is Note
. Choose a fraction. Bound to global Tempo
.
This is the input if the selected Time Unit is Measures
. Duration in Bars. Bound to global Tempo
.
Quantize time. Define the subdivisions per WaveSpline
cycle. A value of zero turns of temporal quantization. Primarily to create Pitch sequences. In theory also acts as sample rate converter for audible signals.
Only visible for non zero Time Grids. Use this to shift the quantized WaveSpline
left and right. Useful to carefully align the pitch waveform of multiple voices.
Quantize value. A value of zero turns of value quantization. Useful if you want pitch steps for example. Acts like a bitrate reducer when applied to a Wave
Generator.
The currently selected voice can be configured in this section. You can manage the generators and parametes of the envelope for example.
Click
to select the Wave Generator and display the according WaveSpline
. The Wave Generator cannot be removed.
The Wave Generator is used to generate the audio output. Both Pitch and Gain Generators can be connected to modify its parameters.
Especially if you decide to disable Protection
be sure that the WaveSpline
crosses zero and the frequencies are not too low in order to prevent damaging your speakers.
The Wave Generator is selected by default. This is what it looks like in its highlighted state.
Click
to select the Pitch Generator. The Pitch generator is optional and not connected per default. The view changes as soon as the Generator is connected.
Primary use is melodic sequencing. Might also be used for frequency modulation.
View when not connected.
View when connected.
Click to create and connect a new WaveSpline Generator that changes the pitch of the Wave Generator of the selected Voice.
Only visible when connected. Click to disconnect and remove the Pitch Generator of the selected Voice.
A multiplier that determines how much octaves the Pitch Generator covers. Negative values reverse the pitch direction.
Click to select the Gain Generator. The Gain generator is optional and not connected per default. The view changes as soon as the Generator is connected.
Use this to add rhytm or for amplitude modulation purposes.
View when not connected
View when connected
Click to create and connect a new WaveSpline Generator that affects the amplitude of the Wave Generator within the selected Voice.
Only visible when connected. Click to disconnect and remove the Pitch Generator of the selected Voice.
Configure the envelope and the mix volume of the selected voice.
Attack time. Time it takes to reach the volume when a voice is spawned or revived.
Sustain Time. Per default the Sustain time is infinite. If you choose a value the voice is automatically killed after the given time. The sustain period starts after the Attack time, or whenever the sustain value is changed.
The time a voice is faded out after being killed. Also applies when the Panic button is pressed.
Additonally to the Gain generator and the envelope setting the Volume value is finally multiplied with the Wave Generator output. Use this to change overall Volume per Voice.
Click this button to spawn a new Voice by cloning the values of the currently selected voice. You want to choose this method, if you do not want to change the configured Wave Generator frequency as it happens when you press a keyboard key.
A Voice
has a life cycle, and within this section you can manage it. The currently selected Voice
is highlighted. Using the Keyboard
or by clicking the Spawn
button you create a clone that immediately appears in this section. Each Voice
can be modified individually as long as it is played back.
Voices contain the Wave
generator and optional Pitch
and Gain
generators. Select a Generator
by clicking on it.
Since Voices are sustained by default you might end up with a bunch of voices while playing on the keyboard. Either choose a Sustain
time, Kill
Voices manually or click the Panic
button to get rid of them all.
The Ether
is the default Voice
and acts as template to spawn new voices. The Grave
holds the last Voice
that died while being selected. Everything you hear is listed right of the grave in the audible voices section.
Click to select the Voice
from the Ether
. Basically a non audible Voice
that acts as default template. Select this, if you want to configure a voice before it is played. After configuring you can play the voice using the Keyboard or the Spawn button.
The Ether
never changes unintentionally.
In this graphics is the selected state of the Ether
as you find it when starting the application.
Similar to the Ether
the Voice
in the Grave
is non audible. Everytime a selected Voice
is killed and died, either manually or by reaching its Sustain
time, it ends up in the Grave
. Use it to respawn a Voice that died.
All voices that are currently active and produce some audio signal are listed here.
Click to select the Voice
. The view changes to display the selected Voice
. Basically the same as the Ether
and Grave
Voices, but currently active and presumably producing some audio output.
Every Voice has an Attack, Hold and Release state.
Audible Voice
while living
Audible Voice
when killed
As soon as you click the kill button the Voice
is released and the view changes. After the Release
time the Voice
is removed from the audible voices section. If it was selected while being removed it will show up in the Grave
, otherwise it is gone.
During the Release Phase you have the opportunity to Revive a Voice. The Voice state changes to Attack, the voice is faded in again.
You might choose from two different Keyboard Modes. Poly
will spawn a new Voice everytime you press a Keyboard Key. Mono
will only spawn on Voice but keep setting the Wave Generator frequency of the selected Voice. You might use the Mono
mode to change the frequency of any Voice selected using the Keyboard.
The following modal pops up when clicking the select button. In this case Poly
is selected.
Somehow self explaining, I guess. Depending on the selected Mode a new Voice is spawned using the currently selected as template or, when in Mono
Mode, the Wave Generator frequency is changed according to the key pressed.
The frequency of the Wave Generator of the currently selected voice is highlighted. In this example c3.
Shifts the Keyboard range octace wise.
Opens up the Keyboard Config Panel to configure the virtual Keyboard.
The Keyboard Config Panel opens up when clicking the config button.
Base frequency from which the Key frequencies are calculated from
Amount of keyboard keys to display.
Number of subdivisions per octave.
Mobile device support is limited. I guess it will work on some devices. On small screens the layout changes. Instead of a list of all WaveSplineNodes
you only see the currently selected WaveSplineNode
below the canvas. You can scroll through the different sections, the position of the header and the keyboard section stays fixed.
I recommend choosing landscape orientation, since the most important features are better accessible. You can scroll down to view the other sections.
While you still can use the application in portrait orientation, I have to admit it does not look beautiful. The header settings are relocated into the Settings
modal and you probably want to reduce the number of keyboard keys in the Keyboard Config Panel
to make it easier to use them.
It was curiosity and the joy in developing software that serves as a tool for creative expression. When I read about the AudioWorklet replacing the ScriptProcessorNode I was excited to try out this new technology. It overcomes the perfomance limitations of the previous approach, perhaps finally sophisticated real-time audio in the browser. I wanted to give it a try. It seemed to be the time for what is now the third revision of a synthesizer, where you can edit the waveform similar to a curve in a vector graphics program.
The AWSM is raw web technology, a pure vanilla shake, no 3rd party libs used. No dependencies other than a modern browser environment.
In theory it does not require a build step utilizing a bundler or transpiler. I cried a while when realizing that Firefox currently does not support ES6 module imports within AudioWorklets. It's a shame and I hope this will change some day. For now I had no better idea to resolve and bundle all imports of this component using webpack, the rest of the app stays untouched and is just copied to the dist folder. For development I use Chrome and a dev server that serves directly from the src directory, no need to wait for a build to complete.
Essentially, there are three distinct loosely coupled development realms. The data model and the interpolation logic, the sound generation and the user interface.
The data model is somewhat old-fashioned. It is an event-driven, object-oriented approach with mutable types and cross referencing support. Maybe I'm wrong, but IMAO this seemed appropriate, flexible and performant.
The sound generation is almost exclusively realized using a monolithic AudioWorklet as oscillator. The processing is done using ECMA script only. The performance can certainly be significantly improved if this part is implemented as a WASM module.
The user interface is built exclusively on Web Components technology. All elements are custom made and the components are coupled to the model via an propietary event system.
I like to utilize my free time projects to deepen my knowledge and try out new stuff. Both things that are often hard to justify when working at a regular basis and for a paying customer. Do not consider anything of the code as best practice, as long as you are not convinced it is.
This is a protoype, a technology demonstrator, nothing stable.
Nevertheless I put quite a lot of effort into this. Everything was built from scratch. It took me over hundred hours to get this project to where it is today. Uncounted the hours I was lost in creating sounds and music with this tool.
For now no further development is planned. Even if I'd love to try out some more ideas and integrate a bunch of further features, I do not have the time to do so. Maybe I'll mess around with this some time in the future. Don't expect anything to stay or change.
I think some crucial features are missing. The list is long, but here are a few of the features I would love to add.
Speaker ProtectionImprove support for smartphones and other small screen size devices- Selection and transformation of multiple WaveSplineNodes at once
- Local storage of multiple configurations using IndexedDB API
- WaveSplineProcessor WASM implementation
- Referencing option when connecting generators
- WaveSpline referencing
- Implement more interpolation algorithms
- MIDI support
- Stereo panning for Voices
- Automate everything
- Electron wrapper
At its core it's just a simple algorithm. The general concept would also work with linear interpolation, but exponential interpolation sounds so much smoother. Modifying the exponent in realtime changes the curve steepness dynamically somehow similar to what a filter does.
The exponential interpolation algorithm outputs y for x that always stays within the given min / max boundaries of a 2d point set.
The interpolation curve is calculate by combining two segments of two x to n degree exponetial functions where x > 0.
This is a simplified example. The curves are actually scaled, moved and limited.
First function
-x^2
|
-------x-a-a--------
x | a
x | a
x | a
x | a
Second function
x^2
b | x
b | x
b | x
b | x
-------b-b-x--------
|
Combined interpolation curve
a a
| a
| a
| a
| a
| b
| b
| b
| b
+-----------------b--b
Pseudo code algorithm including transformation
if x < .5
f(x) = ((x * 2) ^ e) / 2
else
f2(x) = 1 - ((1-x) * 2) ^ e) / 2
The audio processing is decoupled from the rest of the application. The AudioCore manages the playback and state of all audible Voices. Every voice contains a Wave generator that has a optional pitch and gain input that can be connected to the output of another generator.
The user interface does not support it, but Generators can be reused by multiple voices. This applies to almost all data model classes. Even WaveSplineNodes could be used in multiple WaveSplines. You always have the choice of just referencing something.
The AudioCore manages the Voices, and the VoiceInterface retrieves all necessary WaveSplineProcessors from a global processor pool. Processors are created on demand and automatically garbage collected using simple reference counting.
The AWSM produces only a mono output signal the same signal is copied to all channels. The routing is quite straight forward. Note that even if the UI does not allow it, it is possible to use the output of any generator as input parameter for one or more other generators. Pitch and gain generators are optional.
pitch generator => wave generator pitch param
gain generator => wave generator gain param
wave generator => ASR / volume
ASR / volume => voice out
voice out => mix
mix => protection
protection => main out
The user interface largely consists of WebComponents. Abstract, simpler units are called elements, more complex, application-specific ones are reffered to as components.
The component structure of the user interface.
AWSM
HeaderSection
WaveSplineSection
WaveSplineCanvas
WaveSplineNodeList
[WaveSplineNodeListItem]
WaveSplineSettings
WaveSplineViewSettings
VoiceSection
VoiceWaveGenerator
VoicePitchGenerator
VoiceGainGenerator
VoiceSettings
[VoiceListItem]
KeyboardSection
KeyboardSectionSettingsModal
The main components of the data model are as follows.
Configuration
voices: [Voice]
Voice
attack, sustain, release, volume
wave: Generator
pitch: Generator
gain: Generator
Generator
waveSpline: WaveSpline
waveSplineView: WaveSplineView
WaveSpline
type, phase, e
nodes: [WaveSplineNode]
WaveSplineNode
x, y, e
WaveSplineView
frequency, quantizeX, quantizeXThreshold, quantizeY
Feel free to use this tool to make music or to test the limits of your speakers. I encourage this. When redistributing the software, be sure to understand the underlying license. Since I'm donating a considerable amount of my time to the opensource community, it's important to me that everything that builds on this is also available to everyone.
This project is licensed under the GNU General Public License v3.0
Copyright (C) 2022 C. Nicholas Schreiber
See COPYING for the license text or contact me for more information.
The license applies to every file within this repository even if not explicitly stated within the source code of every module.
Official GNU license page: GNU General Public License v3.0