-
Notifications
You must be signed in to change notification settings - Fork 0
Language Documentation
This documentation is a detailed description of the main syntax and characteristics of the language with examples on each section. It's designed for developers and it can be a bit tedious. If you are new with ChordScript, you should start with the tutorial and go back here if you have a specific query.
If you don understand the meaning of any symbol or name used in examples below, please go to the annex section where all language definitions are exposed.
ChordScript is a dynamic, strongly typed and interpreted language, that executes code to generate sounds. In order to execute sounds on different operative systems, it needs to "connect" with different sound systems and drivers.
To simplify this communication, ChordScript uses Jack Audio Connection Kit. It works as a server mounted over current OS sound diver, and ChordScript connects to it as a client. For this reason, when you install CS you also install Jack. This architecture, does not only makes ChordScript able to run on different OS, but also offers the possibility to work with low latency audio generation.
So, CS allows you to trigger pre-recorded samples, synthesize sounds using basic functions and apply some effects on this sounds, using programming as interface.
For this reasons, ChordScript can be a good alternative for live coding, creative coding, algorithmic composition or for any musician/programmer who wants to use programming as a powerful instrument for composition, giving also the possibility to record and store it as a .wav file.
ChordScript takes some syntax rules from other popular languages. This decision was made to take it easy to learn other languages if you start your programming path here. It takes the strong rules of C/C++ and the dynamics of python.
Here is a list with the basic rules:
-
The type of a variable must be specified in assignation:
-
number myVariable = 10;
See data types section for a list of all valid data types
-
Assignations and executions lines, must end with a semicolon:
-
string name = "my name";
-
Variable names must start with a letter and can continue with more letters, numbers or the character underscore '_' :
-
MyVariable_3
Valid name. -
4_aVariable
Invalid name.
There are some code examples that you can copy and paste in your ChordScript IDE and then modify or play to understand or take as a base for your compositions. If you want to install the IDE go to the ChordScript Page.
This demo, shows a lot of capabilities of the language. In order to read simpler examples continue to specific sections.
Base with simple chords
sound mySound = S_SIN * 0.3;
number quarter = 2; # in seconds
number eighth = quarter / 2; # in seconds
array scale = [C5,D5,E5,F5,G5,A5,B5,C6,D6];
# Definition of some chords
array c = [C5,E5,G5];
array a = [C5,E5,A5];
# Another chord but defined using the scale
array d = [scale.at(0),scale.at(3),scale.at(5)];
argument song = (); # This argument holds all the song chords
song.push((c,quarter));
song.push((a,quarter));
song.push((d,quarter));
song.push((c,quarter));
mySound.play(
song
);
# Define a lead sound
sound myLead = S_SQUARE.freqModulation(S_SIN(5) * 0.7);
argument melody = ();
# Play a scale
for(number i = 0; i < 8; i++) {
melody.push(([scale.at(i)],eighth));
}
myLead.play(melody);
Synthesize using loops
sound mySound = S_SIN * S_SIN.freqModulation(S_SIN(4)) * S_SIN.freqFactor(2);
mySound.play([A4]);
mySound.loop(
([D5],1),
([Ds5],0.4)
);
mySound.loop(
([0],0.9),
([Cs5],0.4)
);
There are a list with all data types in ChordScript:
Basics
number
boolean
string
argument
array
-
group
(not implemented yet)
Playables
sound
sample
Some of this data types has defined methods that are listed and explained on each particular section.
Basic types are data types that are useful for programming but has not a particular usage for playing music. These are used as control variables in for loops, as flags in flow control or in general to simplify the algorithmic programming.
In the code, numbers are simply any kind of number, with or without decimal part.
Some examples of numbers could be number myNumber = 1;
or number mySecondNumber = 4.53;
Only a dot can be used to separate decimal part, comma it's not allowed.
Valid operations with numbers are addition +
, subtraction -
, multiplication *
and division /
Additionally you can call the operator ++
and --
to add/subtract one and assign the number again to the same variable.
Boolean values can be expressed as true
or false
.
It can also be expressed as numbers. A false value is 0 and any other number is considered true.
Valid boolean operations are:
-
==
compare if two values are equal and return a boolean as a result of the comparison. -
<
compare if the right values is bigger than the left value. -
>
compare if the left values is bigger than the right value. -
>=
compare if the left values is bigger or equal the right value. -
<=
compare if the left values is smaller or equal the right value. -
!=
compare if both values are different. -
&
Works as the AND boolean operation. If both values are true, so the returned value is true. Otherwise return false. -
|
Works as the OR boolean operation. If both values are false, return false, otherwise return true. -
!
this operator placed before a boolean value, works as the NOT boolean operation.
Strings are text placed between double quotes "I am a string!"
. This is the way to introduce text inside ChordScript. All text between quotes aren't interpreted as code. So, can be used for naming an object or to specify the name of a file in the computer.
Some functions takes a string as an argument, for example: sample mySample = SAMPLE("path/to/my/file.wav");
Strings can be concatenated using the operator +
Argument is an interesting data type, that can hold values to be passed later to a function. An example could be the better explanation:
...
number a = 10;
argument myArgument = (a,1);
a = 13;
myFunction(myArgument); # Here myFunction is called with the argument (10,1)
...
So, this allows you to set argument values before calling the function.
push(argument)
Arguments works as a (stack)[https://en.wikipedia.org/wiki/Stack_(abstract_data_type)]. So, it has push and pop methods. The value passed as argument, is attached as the last element in the argument stack.
argument myArgument = (4, 5, 6);
# Call the method to add another number at the end
myArgument.push( 7 );
# At this moment, the argument is (4, 5, 6, 7)
pop()
Arguments works as a (stack)[https://en.wikipedia.org/wiki/Stack_(abstract_data_type)]. So, it has push and pop methods. This method does not take arguments and it removes the last element of the argument stack.
argument myArgument = (4, 5, 6);
# Call the pop method
myArgument.pop();
# At this moment, the argument is (4, 5)
Arrays are elements between brackets separated by commas. It is a way to store things that can be accessed and removed later. To modify the array, it has some methods detailed below. It works as a (linked list)[https://en.wikipedia.org/wiki/Linked_list] but with some 'overloaded' methods like insertion/deletion in a particular position.
Here is a definition example:
...
# Here we creates an array. It can hold elements of any type.
array myArray = [1, 3, 6, "hi", S_SIN];
...
If you insert a variable inside an array, a copy of the value is inserted. So, you can change the variable value later, but the value in the array will still being the same.
size()
This method returns the number of elements currently stored in the array.
Here is an example usage:
array myArray = [3, 5, 6, 7];
number aNumber = myArray.size();
# aNumber is equal to 4;
at(number)
It takes an integer greater or equal than 0 as argument and returns a copy of the element stored in that position of the array. So you can modify the returned element without changing the stored value. Positions in an array starts at index 0. So, if an array holds 3 elements, the first element is stored in the position 0 and the last one is stored in the position 2.
Here is an example usage:
array myArray = [3, 5, 6];
number aNumber = myArray.at(1);
# aNumber is equal to 5
push(element)
This method insert a copy of the element passed as argument in the last position of the array, increasing the size of the array by one.
Here is an example usage:
array myArray = [3, 5, 6];
myArray.push(7);
# Now, myArray is [3, 5, 6, 7];
insert(number, element)
This method receives two parameters, a position to store the value and the element to store in that position. The number must be greater or equal than 0 because it represents a position of the array. Elements after the position are moved, in order to insert the element in that position. Array size is increased by one.
A copy of the value are inserted, so you can modify the value of the element later and the value inside the array still being the same.
Here is an example usage:
array myArray = [3, 5, 6];
myArray.insert(1, "water");
# Now, myArray is [3, "water", 5, 6];
remove(number)
This method removes an element of the array, so array size is reduced by one. The number received as parameter, must be greater or equal than 0 because it represents a position in the array.
Here is an example usage:
array myArray = [3, 5, 6];
myArray.remove(1);
# Now, myArray is [3, 6];
pop()
This method removes the last element of the array, so array size is reduced by one.
Here is an example usage:
array myArray = [3, 5, 6];
myArray.pop();
# Now, myArray is [3, 5];
This data types allows you to synthesize sounds. This type has the following methods and operations:
play( [array] , number = 0)
As its name says, this method play the sound during a time in seconds specified in the second number parameter. If you call this method without a duration, the sounds play forever (until you stop it).
The first parameter is an array of frequencies of type number
between brackets and separated by commas. All this frequencies are passed to the sound at the same time, so this is the way to play chords for example.
# This line creates a sound using the pre defined sound called S_SIN.
# See synthesis section for more information about sounds.
sound mySound = S_SIN;
# Play a chord of A minor during one second
mySound.play( [A4, C5, E5], 1 );
# This sound is executed at the same time and holds a tone forever.
mySound.play( [A2] );
play( (arguments) )
This is another way to call the play method. It consist of arguments similar to the previous play definition passed as the same time to play one after the other. This is the way to play a song for example.
# Define a sound
sound mySound = S_SIN;
# Call the method to play 3 chords one after the other.
# The total duration of this play is 4 seconds.
mySound.play(
( [A4, C5, E5], 1 ),
( [F4, A4, C5], 1 ),
( [G4, C5, E5], 2 )
);
Important: you can't call this method as the second way and giving no duration on any argument. It throws a syntax error.
stop()
This method doesn't receive any argument. It just stops all generated sounds that are playing of a particular sound
.
# Create a new sound using the base sound S_SQUARE
sound mySound = S_SQUARE;
# Play a 440 A during some time
mySound.play( [440] );
# And some seconds later...
# Stop the sound
mySound.stop();
Note: if you copy and paste this code and run, you won't hear anything because you are playing and stopping the sound immediately after that. This method is useful when live coding, and you want to stop some sounds that are currently playing.
loop( [array] , number ), loop( (arguments) )
This method is similar to play
but with the difference that when all arguments are played, starts again infinitely.
# Define a sound
sound mySound = S_SIN;
# Call the method to loop this 3 chords.
mySound.loop(
( [A4, C5, E5], 1 ),
( [F4, A4, C5], 1 ),
( [G4, C5, E5], 2 )
);
This 3 chords will play in a loop indefinitely until you stop the sound.
setPanning( number )
This method receives a number between 0 and 1 representing the panning of the sound. 1 is the stereo right channel and 0 the left one. So, 0.5 is the default centered sound.
This method returns a new sound with the same characteristics of the current one but with the new panning. To understand how to use it, go to Synthesis section
# Define a sound completely panned to the right channel
sound mySound = S_SIN.setPanning(1);
# Play this chords in the right channel
mySound.play( [A4, C5, E5], 1 );
setPanning( sound )
Optionally you can call this method but giving another sound as parameter. This generates a panning effect based on the sound passed as argument.
This method returns a new sound with the same characteristics of the current one but with the new panning. To understand how to use it, go to Synthesis section.
# Define a simple sound
sound mySound = S_SIN;
# Define a sound with panning controlled by the other sound
sound myPannedSound = S_SIN.setPanning(mySound);
# Play this bass with a fast panning change between channels during 1 second
myPannedSound.play( [100], 1 );
constantFreq( number )
This method receives a constant frequency number as parameter and returns a new sound with the same characteristics of the current sound, but with a constant frequency. This means that no matter the frequencies you sent when you call play or loop method, it always play the same note.
This is useful for synthesis. See Synthesis section for more interesting examples.
# Define a sound with a constant frequency
sound mySound = S_SIN.constantFreq( A4 );
# Play A4 during 1 second (instead of E3)
mySound.play( [E3], 2 );
Additionally you can call this method as an anonymous method with the following syntax:
# Define a sound with a constant frequency
sound mySound = S_SIN( A4 );
# Play A4 during 1 second (instead of E3)
mySound.play( [E3], 2 );
freqFactor( number )
This method set a factor number multiplying the frequency of the sound. Receives a number representing this factor, and returns a new sound with this factor set. To see more examples, see Synthesis section
It can be used to octave a sound for example.
# Define a sound with a frequency factor set
sound mySound = S_SIN.freqFactor( 2 );
# Play E4 instead of E3 during 2 seconds
mySound.play( [E3], 2 );
freqModulation( sound )
This method modulates a copy of the current sound with the sound passed as argument using frequency modulation and return this copy. To see more examples, see Synthesis section
It works as a LFO controlling the frequency of the original sound.
# Define a basic sound with a constant freq of 10Hz
sound myLFO = S_SIN( 10 );
# Creates a new sound modulated using myLFO
sound mySound = S_SIN.freqModulation(myLFO);
# Play a note during 2 seconds
mySound.play( [E3], 2 );
ampFactor( number )
This method receives a number
that multiply the amplitude and return a new sound with this value set. For example, a factor of 0.5 will reduce the volume of the sound to the half. To see more examples, see Synthesis section.
This is the best way to set the volume of a sound.
Additionally you can call this method using the operator *
or /
:
# Define a sound that will be played 0.3 times low.
sound mySound = S_SIN.ampFactor( 0.3 );
# This line generates the same result.
mySound = S_SIN * 0.3;
# Play a note during 2 seconds
mySound.play( [E3], 2 );
ampFactor( sound )
This method can be called giving a sound as a parameter and return a new sound modulated using amplitude modulation. To see more examples, see Synthesis section.
It works as a LFO controlling the amplitude of the original sound.
Additionally you can call this method using the operator *
:
# Define a sound with a small constant frequency (5 Hz)
sound myLFO = S_SIN(5);
# Use this LFO to modulate a "carrier" of a higher frequency
sound mySound = S_SIN.ampFactor(myLFO);
# This line generates the same result.
mySound = S_SIN * myLFO;
# Play a note during 2 seconds
mySound.play( [E3], 2 );
The operator +
is implemented to add sounds. It means to add point-by-point each sample. The resulting sound will be a new sound with higher total amplitude, so take care to multiply this sound with a reduced factor to avoid saturation.
Sample is a type to store a .wav file and allows you to play it modifying some parameters.
To load a sample from a file in your computer, you must use the function SAMPLE()
:
# Define a sample variable
sample mySample = SAMPLE("path/to/my/sample.wav");
## In future versions there will be some samples pre loaded with the interpreter
This data type has the following methods and operators:
play( number = 1 )
This method play the sample once and takes one parameter. It is a time factor that multiplies the reproduction speed of the sample. By default this value is 1. For example if you call this method with a 0.5 the sample is played at half normal speed.
# Define a sample variable
sample mySample = SAMPLE("path/to/my/sample.wav");
# Play the sample once
mySample.play();
# Play the sample once at the same time but at double speed
mySample.play(2);
Note: If time factor is 0, no sample is played
play( ( arguments ) )
Optionally you can call this method with arguments separated by commas. Each argument can have 2 numeric parameters. The first one is the time factor explained in the previous paragraph and the second is a "virtual duration". It's the duration in seconds to wait before play the next argument. It seems a bit strange, but an example could help to understand:
# Define a sample variable
sample mySample = SAMPLE("path/to/my/sample.wav");
# Lets suppose that this sample has a duration of 2 seconds
mySample.play(
(1), # Play the sample with no alterations
(1, 1), # Play the sample normally but with a virtual duration of 1 second
(1, 3), # Play the sample again but it starts 1 second before the last one ends
(), # Play the sample 1 second (2s + 1s = 3s) after the last ends.
);
stop()
This method doesn't receive any argument. It just stops all playing versions of this sample variable.
# Create a new sample
sample mySample = SAMPLE("path/to/my/sample.wav");
# Play the samples
mySample.play();
# And some seconds later...
# Stop the sample
mySample.stop();
Note: if you copy and paste this code and run, you won't hear anything because you are playing and stopping the sound immediately after that. This method is useful when live coding, and you want to stop some sounds that are currently playing.
loop( number ), loop( (arguments) )
This method is similar to play
but with the difference that when all arguments are played, starts again infinitely.
# Create a new sample
sample mySample = SAMPLE("path/to/my/sample.wav");
mySample.loop(
(1),
(1, 3),
(),
);
This 3 chords will play in a loop indefinitely until you stop the sound.
setPanning( number )
This method receives a number between 0 and 1 representing the panning of the sound. 1 is the stereo right channel and 0 the left one. So, 0.5 is the default centered sound.
This method modifies the current sample and return null
# Create a new sample
sample mySample = SAMPLE("path/to/my/sample.wav");
mySample.setPanning(1);
# Play the sample in the right channel
mySample.play();
setPanning( sound )
Optionally you can call this method but giving a sound as parameter. This generates a panning effect based on the sound passed as argument.
# Define a simple sound
sound mySound = S_SIN(100);
# Define a sample with panning controlled by the other sound
sample myPannedSample = SAMPLE("path/to/my/sample.wav");
myPannedSample.setPanning(mySound);
# Play this sample with a fast panning change between channels during 1 second
myPannedSample.play();
Flow control are statements to control the execution of the code. In ChordScript, there are two statements:
if
for
If statement is the most basic control flow. It checks the boolean comparison and if the result is true
, code between braces are executed. Otherwise, code between braces are ignored.
if( boolean comparison ) { code }
An example of usage:
number a = 5;
if( a < 10 ) {
...
# a is less than 10, so this code is executed
...
}
# Code continues execution after executing the code inside if braces
Optionally, another keyword can be added after closing the braces. An else
statement:
number a = 5;
if( a < 10 ) {
...
# a is less than 10, so this code is executed
...
}
else {
...
# If a is bigger than 10, this code is executed.
...
}
# More code...
Between braces, a new scope is created. It means that all variables created inside it, are only valid between braces.
For statement is a loop that iterates over a variable in order to control the number of iterations. The syntax is as follows:
for( assignation ; boolean comparison ; assignation ) { code }
The most common usage is something like this:
for( number i = 0; i < 10 ; i++ ) { ... }
Steps of executions are:
- First, a variable
i
is created equal to 0. - Comparison is evaluated. If true execute braces, else continue executing code after close brace.
- Execute the code between braces.
- Execute the second assignation (
i++ (i = i + 1)
). - Go back to step 2.
Between braces, a new scope is created. It means that all variables created inside this loop, are only valid between braces.
Functions are pieces of code that are encapsulated to be executed later (more than one time).
Examples of functions are on this language when you create a sample SAMPLE()
. Basically functions have a name, can take arguments and can return a value.
Calling a function, means:
# Create a sample variable without a value.
sample mySample;
# Then call the function SAMPLE() to obtain a value for mySample.
# SAMPLE, receives a string argument.
mySample = SAMPLE("path/to/file.wav");
ChordScript gives you a possibility to create your custom functions. The syntax to define a function is the following:
# The first word is the data type returned by the function.
sound myFunction( sound aSound ) { # This function receives a sound as parameter and can be used inside the function with the name "aSound"
# Some code that can use the sound received as parameter.
return aSound.setPanning(0.2); # This function must return a number, because its the defined data type
}
Additionally, a function can return a null
value. In that case, return keyword must be written without any value: return;
Between braces, a new scope is created. It means that all variables created inside it, are only valid between braces.
Synthesis, is the process to create a new sound operating simpler sounds. It can be as complex as you want, and a lot of concepts can be applied to synthesize professionally. Basically, all operations and methods explained in the sound type are useful to synthesize. An interesting concept you may read about is fourier series, and visit the synthesis tutorial.
ChordScript defines some variables and functions to simplify and make easy to program. Here is a list of defined variables:
Notes
Notes are defined to replace frequencies and uses a simple nomenclature. Starting from C0 (the lowest C of the first octave) and ends in B8 (the highest B in the ninth octave)
C0
-
Cs0
=Db0
D0
-
Ds0
=Eb0
- ...
-
As8
=Bb8
B8
Sounds
As you see, sounds can be combined to generate new sounds, but basic sounds are:
-
S_SIN
a sinusoidal wave -
S_SQUARE
a squared wave - New sounds incoming..
This is the list of defined functions:
-
sample SAMPLE( string )
receives the path of a file and returns a sample -
null STOP()
stops all the sound -
null START_RECORDING()
start recording all sound to be stored in a file -
null STOP_RECORDING()
stop recording and dump the .wav file -
null START_SERVER()
start the Jack server if it's not running -
null RESTART_SERVER()
stop and start the server again -
null KILL_SERVER()
stop the Jack server