Dittytoy allows you to create generative music using a minimalistic javascript API.
All music on Dittytoy is generated entirely in code. So don't expect easy-to-use audio nodes to connect with virtual wires or a graphical user interface that allows you to define patterns easily: everything is code.
Dittytoy gives you complete freedom to generate sound. If you want to write a function that generates the entire sound wave of your ditty, you can. In that case, open this boilerplate and get started: #10 Minimal Sound Wave.
However, Dittytoy has several high-level building blocks that make creating sounds, musical patterns, and filters easier. We will explain these tools below. The API syntax is loosely based on the syntax of Sonic Pi.
If you don't like reading documentation and want to see sample code, then these ditties give a good overview of the capabilities of Dittytoy.
To create a new ditty, click the New ditty link and modify the example in the editor to the right.
When creating music on Dittytoy, a number of basic building blocks can be used:
And, there are some other useful concepts and built-in methods and variables that you can use to create your ditty:
The first building block for creating generative music on Dittytoy is the synthesizer. The synthesizer is a function (or class) that generates a sound. You can create a synthesizer as follows:
const osc = synth.def( (phase, env, tick, options) => Math.sin(phase * 2 * Math.PI) * env.value );
Note how the return value of the lambda function is multiplied by the env(elope) value. You don't have to do this, but if you do, your synth will work seamlessly with the built-in support for envelopes.
The synthesizer can be triggered using the play method. The following code generates a short sound of a sine
wave with pitch c4
:
const osc = synth.def( (phase, env, tick, options) => Math.sin(phase * 2 * Math.PI) * env.value );
osc.play(c4);
You may want to define a more complicated synthesizer that has its own state. This can be done by
passing not a lambda function, but a class to synth.def()
. For example:
const osc = synth.def(
class {
constructor(options) {
// The value of the note argument of the play call is retrievable via options.note.
this.phase = 0;
}
process(note, env, tick, options) {
this.phase += midi_to_hz(note) * ditty.dt * (env.value ** options.xenv);
return Math.sin(this.phase * Math.PI * 2) * env.value;
}
}, { xenv: 0.5, amp: 1, attack: 0.01, release: 0.3 }
);
osc.play(c4, { pan: 0.5} );
Every time you call the play method a new object is created. You can retrieve the value of the note argument of the play call via options.note
in the constructor of the synth class.
As you can see in the code examples above, you can set options when calling the play
method (as a second argument) and when defining a synthesizer.
The options set when defining the synthesizer are the default options for this synthesizer. You can always override
these options later on a specific play call.
Default options are:
name
(optional, the name of the synthesizer)amp
(volume, default: 1)pan
(panning, -1 to 1, default: 0)env
(envelope, default: adsr)When defining a new synthesizer (or a filter or loop), you are free to add your own properties to the options object.
When you define a synth, or when you call the play method, you can also set the envelope options. For the default ADSR envelope (see envelopes), you can set the following options:
attack
(in seconds, default: 0)decay
(in seconds, default: 0)sustain
(default: 1)release
(in seconds, default: 0.5)curve
(default: -2, the curvature value for the decay and release phase)duration
(in ticks, default: 0)This ditty gives a clear illustration of the mechanics of the ADSR envelope: #14 ADSR Envelope.
Note that you can also assign a lambda function ((tick, options) => ...
) to an option, so you can have
continually changing properties.
The following code will play a sin wave with pitch c4
that pans from left to right in one tick:
sine.play(c4, { pan: (tick, options) => Math.sin(tick * Math.PI), duration: 2 } );
As you can see, some of the envelope option properties are in ticks, while others are expressed in seconds. Dittytoy uses both ticks and seconds. So pay close attention when you call or implement a particular function, and check the time unit of the variables used.
A tick (or beat or a quarter note) is inversely proportional to the number of beats per minute, which you can set
with ditty.bpm
. The default bpm of a ditty is 120, in which case a tick will have a duration of 0.5
seconds.
You can use the built in functions tick_to_second(ticks)
and second_to_tick(seconds)
to convert ticks to seconds and back.
When calling the play method on a synth, you can enter the note as a midi number, but all notes are also pre-defined
as a variable (from C0
to B9
).
If you want to make a note sharp, add an s after the note name such as Fs3
and if you want to make a
note flat, add a b such as Eb3
.
Octave number | Note number | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
C | Db | D | Eb | E | F | Gb | G | Ab | A | Bb | B | |
-1 | 0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
0 | 12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
1 | 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
2 | 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
3 | 48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
4 | 60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
5 | 72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
6 | 84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
7 | 96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
8 | 108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
9 | 120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
You can use the built in functions hz_to_midi(hz)
and midi_to_hz(midi)
to convert midi-numbers to frequencies and back.
There are a lot of different scales and chords predefined in Dittytoy. You can use
the chord
function to create an array with the notes of a chord:
function chord(baseNote, notes) // returns an array with notes of the chord
// this will give you an array with the notes of a c4 major chord:
const notes = chord(c4, chords['major']);
You can also create arrays with the notes of different scales using the scale
function:
function scale(baseNote, notes, octaves = 1) // returns an array with notes of the scale
// this will give you an array with the notes of three octaves of the e4 minor pentatonic scale:
const notes = scale(e4, scales['minor_pentatonic'], 3);
A full list of all predefined chords and scales can be found here.
In the pieces of sample code above, a single (mono) wave function is created by the synthesizer. You can also
return an array [left, right]
for a stereo sound, for example:
const tune = synth.def( (phase, tick, env, options) => [Math.sin(phase * Math.PI * 2) * env.value, Math.cos(phase * Math.PI * 2) * env.value]);
The simplest code on Dittytoy to play a sound is:
synth.def( () => Math.sin(phase * 2 * Math.PI) ).play(c4, {env: one});
The {env: one}
option indicates that the one-envelope should be used:
an envelope with constant value 1.
One synth is built into in Dittytoy and available to use: the sine-synth that will generate a sine wave function.
sine.play(c4, { amp: .5} );
Often you learn the most by studying existing code. Please have a look at these examples:
A second optional but important building block of Dittytoy is the loop. You can use a loop to define a repeating music pattern:
loop( (loopCount) => {
for (let i=0; i<4; i++) {
sine.play(c4, { attack: 0.01, release: 0.25, duration: 0.125, pan: Math.random() * 2 - 1, amp: 1.0 });
sleep( 0.25 );
}
sine.play(d4, { attack: 0.01, release: 0.25, duration: 0.25 }); // attack and release in seconds, duration in ticks
sleep(0.5); // sleep in ticks
sine.play(f4, { attack: 0.01, release: 0.75, duration: 0.25 });
sleep(0.5);
}, { name: 'my first loop' });
A loop often combines play calls and sleep commands. Note that the argument of the sleep command is in ticks, so its duration depends on the beats per minute.
If you want to keep track of a state in a loop, you can also use a class for your loop:
loop(
class {
constructor(options) {
this.note = c4;
}
process(loopCount) {
sine.play(this.note, { attack: 0.01, release: 0.75 });
this.note = (this.note+1) % 12;
sleep(0.5);
}
},
{ amp: 0.5, name: 'loop with class' }
);
The duration of a loop (in ticks) is equal to the total sleep time of all sleep function calls in the loop combined. Note that calling a play method on a synth does not take time and is independent of the duration of the note being played. Thus, calling two play methods directly after each other in a loop will trigger the synths simultaneously.
It is also possible to set the duration of a loop using the sync
option (see below).
A loop supports the following options by default (but you are free to add extra option properties):
name
(optional, the name of the loop)amp
(volume, default: 1)pan
(panning, -1 to 1, default: 0)sync
(in ticks, optional)
You can set the sync
option to define the duration of the loop. So when, for example, sync
is set to 4, the loop runs for precisely four ticks and then starts over. When sync
is set, all play
commands after the duration of the loop are ignored.
Each loop runs in its own worker/thread. So by using multiple loops, you can parallelize your ditty, which makes it a convenient way to optimize your ditty if it doesn't perform well.
A side effect of having each loop run in a separate worker is that it captures all global variables. This makes it impossible to use (global) variables to communicate between different loops.
The sleep function in the loop is a strange construction for users who know javascript well. After all, in native javascript, there is no sleep function. Therefore, to avoid counter-intuitive results, it is crucial to understand what happens behind the scenes in Dittytoy.
Every time Dittytoy starts a new iteration of a loop, the entire body of the loop is evaluated instantaneously. All play-calls are collected and sorted based on the intervening sleep times and put on a timeline before playback starts.
A side effect of having the body of a loop evaluated in its entirety is that if you assign a value to a variable within a loop at different times, it thus has the last assigned value after the evaluation. This causes problems if, for example, you want to write a loop where the synthesizer slides from one note to another using a smoother object that lerps a current value to the target value:
// This will not work, because target is equal to b3 during the whole playback of the loop.
let target = c4;
loop( (loopCount) => {
sine.play(() => smoother.update(target), { duration: 4 } );
sleep( 1.0 );
target = d4;
sleep( 1.0 );
target = e4;
sleep( 1.0 );
target = b3;
sleep( 1.0 );
});
One possible solution to this problem is not to use a separate variable but to create an additional property in the options object. When setting an options property value, it tracks the time of the setter within the loop. The actual assignment of the value is then, like the play-calls, put on a timeline and executed later at the appropriate time. See #17 Portamento:
loop( (loopCount) => {
const s = sine.play((tick, options) => smoother.update(options.target), { duration: 4, target: c4 } );
sleep( 1.0 );
s.options.target = d4;
sleep( 1.0 );
s.options.target = e4;
sleep( 1.0 );
s.options.target = b3;
sleep( 1.0 );
});
Often you learn the most by studying existing code. Please have a look at these examples:
Dittytoy uses envelopes to control the amplitude and duration of a synth's play call.
You can define your own envelope using env.def()
. An envelope is a class that implements a duration
and a value
method:
const sinenv = env.def(
class {
constructor(options) {
}
duration(options) {
return options.duration;
}
value(tick, options) {
return .5 - .5 * Math.cos(tick / options.duration * 2 * Math.PI);
}
},
{ duration: 1, name: 'sinenv' }
);
sine.play( c4, { env: sinenv } );
For each play call, an envelope is automatically created to control the duration of the sound. If you want to
manually create and use an envelope, you can. You can create an envelope object using the create
method
on the envelope definition:
const myEnvelope = adsr.create( { attack: 0.1, decay: 0.2, release: 0.2, duration: 1 });
An envelope object has a value
and a duration
property. Note that you have to sample the
value
property every frame to make sure the envelope is correctly updated.
See this example ditty: #13 Advanced Envelopes
There are four built-in envelopes available in Dittytoy:
adsr
(attack, decay, sustain, duration, release)adsr2
(attack, decay, sustain, release, attack_level, decay_level, sustain_level)
segenv
one
The default envelope in Dittytoy is the ADSR envelope ({ env: adsr }
).
Available options when using the ADSR envelope are (see: #14 ADSR Envelope ):
attack
(in seconds, default: 0)decay
(in seconds, default: 0)sustain
(default: 1)release
(in seconds, default: 0.5)curve
(default: -2, the curvature value for the decay and release phase)duration
(in ticks, default: 0)
You can also set an array [0, -2, -2]
to the curve option to separately set the curvature values for
the attack, decay, and release phases.
As you can see, some of the envelope option properties are in ticks, while others are expressed in seconds. Dittytoy uses both ticks and seconds. So pay close attention when you call or implement a particular function, and check the time unit of the variables used.
Another built-in envelope is the ADSR2 envelope. The ADSR2 envelope is added to Dittytoy to have an envelope compatible with the default envelope used in Sonic Pi. The ADSR2 envelope works similarly to the ADSR envelope, but the most significant difference is that all durations are in ticks.
You can enable the ADSR2 envelope by setting { env: adsr2 }
when defining a synth or playing a note.
Available options when using the ADSR2 envelope are (see: #15 ADSR2 enveloppe):
attack
(in ticks, default: 0)attack_level
(default: 1)decay
(in ticks, default: 0)decay_level
(default: attack_level)sustain
(in ticks, default: 0)sustain_level
(default: decay_level)release
(in ticks, default: 1)The segmented envelope is the most flexible built-in envelope available. You can set the number and levels of segments yourself and independently set the curvature value for each segment. This envelope is similar to the segmented envelope used in Super Collider.
You can enable the segmented envelope by setting { env: segenv }
when defining a synth or playing a
note.
Available options when using the segmented envelope are (see: #16 Segmented envelope):
levels
(default: [0, 1, 0.5, 0])times
(in seconds, default: [0.1, 0.1, 0.25])curves
(default: [0, -2, -2])releaseNode
(default: 2)duration
(in ticks, default: 0.5)
The one envelope is an envelope with a constant value of 1. You can enable the One envelope by setting { env:
one }
when defining a synth or playing a note.
Available options when using the one envelope are:
duration
(in ticks, default: 1e5)Often you learn the most by studying existing code. Please have a look at these examples:
A filter is a function that modifies a signal. You can connect a chain of filters to a loop or other filters using
the connect
method.
Similar to synths and envelopes,
you can define a filter using filter.def()
, and a simple lambda function (input, options) =>
output [l, r]
:
const flipChannels = filter.def( (input, options) => [input[0], - input[1]], {});
Note: the input of a filter will always be a stereo signal, and you always have to return a stereo signal.
If you want to keep a state (for example, when creating a delay effect), you can also define a filter using a class
that implements a process
method:
const lowpass = filter.def(class {
constructor(options) {
this.hist0 = [0, 0];
this.hist1 = [0, 0];
}
process(input, options) {
const alpha = options.cutoff;
if (input) {
for (let i = 0; i < 2; i++) {
this.hist0[i] += alpha * (input[i] - this.hist0[i]);
this.hist1[i] += alpha * (this.hist0[i] - this.hist1[i]);
}
return this.hist1;
}
}
}, { cutoff: 1 });
An example ditty using the two example filters above might look like this:
input.cutoff = 1;
const kick = synth.def( (phase, env, tick, options) => Math.sin(phase * 2 * Math.PI * (1.5 - tick * 4)) * env.value);
const tri = synth.def( (phase, env, tick, options) => { const v = (phase % 1) * 4; return (v < 2 ? v - 1 : 3 - v) * env.value }, { attack: 0.05, release: 0.2, amp: 0.8 });
ditty.bpm = 140;
loop( (loopCount) => {
kick.play(c, { attack: 0.05, release: 0.2, amp: 2 });
}, { amp: 0.75, sync: 1 })
// chain two filters to one loop
.connect( flipChannels.create() ).connect( lowpass.create( { cutoff: () => input.cutoff } ));
loop( (loopCount) => {
scale(c, scales.minor_pentatonic, 1).forEach( note => {
tri.play(note, { pan: Math.random() * 2 - 1 });
sleep(0.25);
});
}).connect( lowpass.create( { cutoff: () => input.cutoff } ));
In Dittytoy, each loop runs in a separate thread/worker. All filters chained to the
loop run, by default, in the same thread.
Sometimes, however, you want to run a filter in a separate thread. This can be for performance reasons (although
each additional thread also gives additional overhead). However, the most likely situation is that you want to
process the output of multiple loops through the same filter. You can enforce this by creating a filter with the
createShared()
method (instead of create()
).
You can connect a (shared) filter to other (shared) filters using the connect
method:
// create a shared filter and connect it to another filter
const shared = lowpass.createShared( { cutoff: () => input.cutoff } ).connect( flipChannels.create() );
// create two loops that both use the same shared filter
loop( (loopCount) => {
kick.play(c, { attack: 0.05, release: 0.2, amp: 2 });
}, { amp: 0.75, sync: 1 }).connect( shared );
loop( (loopCount) => {
scale(c, scales.minor_pentatonic, 1).forEach( note => {
tri.play(note, { pan: Math.random() * 2 - 1 });
sleep(0.25);
});
}).connect( shared);
Often you learn the most by studying existing code. Please have a look at these examples:
The most commonly used construct on Dittytoy is the options object. You use it to set the default options when you define a new synth, filter, or envelope, and you use it to set specific options at the play-call of a synth or when you create a filter, envelope, or loop.
The options object contains key-value pairs { key: value, ... }
. Values are often a constant but can
also be dynamic.
A powerful feature of the options object is that you can not only assign a constant value to a property, but a
lambda function in the form (tick, options) => ...
as well.
This makes it possible to create dynamic properties that change over time, which makes it very easy to create, for
example, a portamento or a Shepard tone.
The note argument of the play call on a synthesizer definition is, behind the scenes, an option property as well (it is an alias for options.note). This also makes it possible to use a lambda function instead of a fixed midi number when triggering the play call.
The following code will play a sin wave with pitch c4
that pans from left to right in one tick:
sine.play(c4, { pan: (tick, options) => Math.sin(tick * Math.PI), duration: 2 } );
And a Shepard tone (original by athibaul):
for(let i=0; i<6; i++) {
sine.play( (t) => (40 + (t + 12*i)%72), {env:one, amp: (t) => Math.sin(Math.PI*(t+12*i)/72) * .25} );
}
Another important property of the options object is that when you assign a value to a property, it keeps track of the time of this assignment within the loop. Read why this is useful here: Loop evaluation and a Portamento.
Often you learn the most by studying existing code. Please have a look at these examples:
You can automatically create a (limited) GUI that controls variables in your ditty by using input parameters. You do this by adding properties to the input object. For each new property, a slider (or pull-down) becomes visible to the left of your ditty.
input.value = x;
By default, the slider of an input parameter runs from 0 to 1. If you want a different range (or step size), you must add a specially formatted comment after the input parameter declaration:
input.value = x; // min=x, max=x, step=x
Finally, it is also possible to fill a pull-down for an input parameter with a limited number of steps.
input.value = 0; // value=0, min=0, max=1, step=1 (No, Yes)
Note! Updating the input variables from user input is asynchronous and will not happen simultaneously for all loops. So if you want to change, for example, the beats per minute using an input variable, you can be sure that your loops will be out of sync very quickly.
Often you learn the most by studying existing code. Please have a look at these examples:
We optimize Dittytoy as much as possible. However, CPU will always be a limiting factor when you want to execute javascript 44100 times per second in the browser.
If your ditty works too slowly and the music stutters, it's time to optimize. Step one is to figure out which part of your code is responsible for the slowness. Generally, this will be code from a synthesizer, as it is most likely to be executed the most. Obvious optimizations in that case are:
Should you have one or more heavy filters in your ditty, you can move them to a separate worker by creating a shared filter.
The following functions are available when writing a ditty;
function hz_to_midi( hz );
function midi_to_hz( midi );
function clamp( t, min, max );
function clamp01( t );
function lerp( a, b, t );
function tick_to_second( t );
function second_to_tick( s );
The ditty object contains mostly read-only properties that describe the global state of the ditty.
The only property that is (meaningfully) customizable is ditty.bpm
.
You can use ditty.bpm
to set the number of beats per minute and, thus, implicitly, the duration of a tick.
Keep in mind that each loop runs in its worker and thereby captures all global
variables.
This also applies to the ditty object. So when you modify ditty.bpm
within a loop, this will not affect
the ditty.bpm
in other loops or the global context.
ditty.bpm // read/write - beats per minute, default: 120
ditty.time // read only, time from start in seconds
ditty.dt // read only, delta time (1 / ditty.sampleRate)
ditty.tick // read only, ticks from start
ditty.dtick // read only, delta tick (ditty.dt * ditty.bpm / 60)
ditty.sampleRate // read only, samples per second of audio
Debugging your ditty while it runs 44100 times per second can be pretty hard. Unfortunately, you can't set breakpoints, and writing values to the default console.log at this rate will most likely crash your browser.
However, Dittytoy allows you to log values to the console at the left of your screen. You can also log values to probes.
// debug log functionality. The log appears in the console at the left of your ditty. You can use:
debug.log ( label, message )
debug.warn ( label, message )
debug.error( label, message )
// debug probes (Safari not supported!). For each label an oscilloscope will appear left of your ditty.
//
// debug.probe( label, value, amp (optional), duration (horizontal resolution in seconds, optional) );
//
// a probe expects one value per sample (more will be ignored, less will pause the probe)
debug.probe( label, value, amp, duration );
Similar to Sonic Pi, several extensions have been added to the
array class, making it easy to loop through an array or choose a random element.
Note that these extensions also work on a string.
// Loop over array. Same as: array[ index % array.length ]
[1, 2, 3, ...].ring(index)
// Choose a random element from the array
[1, 2, 3, ...].choose()
// Mirror an array. [1, 2, 3].mirror() will return [1, 2, 3, 3, 2, 1]
[1, 2, 3, ...].mirror()
There are a lot of different scales and chords predefined in Dittytoy. You can use the chord
function
to create an array with the notes of a chord:
function chord(baseNote, notes = [0]) // returns an array with notes of the chord
// this will give you an array with the notes of a c4 major chord:
const notes = chord(c4, chords['major']);
Predefined chords | |||||
---|---|---|---|---|---|
chords['+5'] |
chords['1'] |
chords['11'] |
chords['11+'] |
chords['13'] |
chords['5'] |
chords['6'] |
chords['6*9'] |
chords['7'] |
chords['7+5'] |
chords['7+5-9'] |
chords['7-10'] |
chords['7-11'] |
chords['7-13'] |
chords['7-5'] |
chords['7-9'] |
chords['7sus2'] |
chords['7sus4'] |
chords['9'] |
chords['9+5'] |
chords['9sus4'] |
chords['a'] |
chords['add11'] |
chords['add13'] |
chords['add2'] |
chords['add4'] |
chords['add9'] |
chords['augmented'] |
chords['dim'] |
chords['dim7'] |
chords['diminished'] |
chords['diminished7'] |
chords['dom7'] |
chords['halfdim'] |
chords['halfdiminished'] |
chords['i'] |
chords['i7'] |
chords['m'] |
chords['m+5'] |
chords['m11'] |
chords['m11+'] |
chords['m13'] |
chords['m6'] |
chords['m6*9'] |
chords['m7'] |
chords['m7+5'] |
chords['m7+5-9'] |
chords['m7+9'] |
chords['m7-5'] |
chords['m7-9'] |
chords['m7b5'] |
chords['m9'] |
chords['m9+5'] |
chords['madd11'] |
chords['madd13'] |
chords['madd2'] |
chords['madd4'] |
chords['madd9'] |
chords['maj'] |
chords['maj11'] |
chords['maj9'] |
chords['major'] |
chords['major7'] |
chords['min'] |
chords['minor'] |
chords['minor7'] |
chords['sus2'] |
chords['sus4'] |
You can also create arrays with the notes of different scales using the scale
function:
function scale(baseNote, notes = [], octaves = 1) // returns an array with notes of the scale
// this will give you an array with the notes of three octaves of the e4 minor pentatonic scale:
const notes = scale(e4, scales['minor_pentatonic'], 3);
Predefined scale intervals | ||||
---|---|---|---|---|
scales['aeolian'] |
scales['ahirbhairav'] |
scales['augmented'] |
scales['augmented2'] |
scales['bartok'] |
scales['bhairav'] |
scales['blues_major'] |
scales['blues_minor'] |
scales['chinese'] |
scales['chromatic'] |
scales['diatonic'] |
scales['diminished'] |
scales['diminished2'] |
scales['dorian'] |
scales['egyptian'] |
scales['enigmatic'] |
scales['gong'] |
scales['harmonic_major'] |
scales['harmonic_minor'] |
scales['hex_aeolian'] |
scales['hex_dorian'] |
scales['hex_major6'] |
scales['hex_major7'] |
scales['hex_phrygian'] |
scales['hex_sus'] |
scales['hindu'] |
scales['hirajoshi'] |
scales['hungarian_minor'] |
scales['indian'] |
scales['ionian'] |
scales['iwato'] |
scales['jiao'] |
scales['kumoi'] |
scales['leading_whole'] |
scales['locrian'] |
scales['locrian_major'] |
scales['lydian'] |
scales['lydian_minor'] |
scales['major'] |
scales['major_pentatonic'] |
scales['marva'] |
scales['melodic_major'] |
scales['melodic_minor'] |
scales['melodic_minor_asc'] |
scales['melodic_minor_desc'] |
scales['messiaen1'] |
scales['messiaen2'] |
scales['messiaen3'] |
scales['messiaen4'] |
scales['messiaen5'] |
scales['messiaen6'] |
scales['messiaen7'] |
scales['minor'] |
scales['minor_pentatonic'] |
scales['mixolydian'] |
scales['neapolitan_major'] |
scales['neapolitan_minor'] |
scales['octatonic'] |
scales['pelog'] |
scales['phrygian'] |
scales['prometheus'] |
scales['purvi'] |
scales['ritusen'] |
scales['romanian_minor'] |
scales['scriabin'] |
scales['shang'] |
scales['spanish'] |
scales['super_locrian'] |
scales['todi'] |
scales['whole'] |
scales['whole_tone'] |
scales['yu'] |
scales['zhi'] |
Often you learn the most by studying existing code. Please have a look at these examples: