A Gwion 'Hello, World'

Let's get started using gwion!

A simple example, step by step

We are going to write a typical Gwion Hello, World!

Prepare to make sound

In Gwion, a program typically makes sound.

Request needed plugin

Now we create a Sinusoidal generator and connect it to thee audio output (dac stands for Digital to Audio Converter) using the ~> operator.

#import Modules
new SinOsc ~> dac;

the SinOsc class comes from the Modules plugin

Greet the world

Indeed, a Hello, world has to print something.
We do so using the gack operator (<<< ... >>>)

<<< "Hello, World!" >>>;

Let time pass

Let's face it, we did not make any sound yet: No time passed, and if no time passed, there can't have been any sound. We can wait some time using

5::second => now;

Try it

Here is the complete example, editable and runnable.
Have fun!

#import Modules

<<< "Hello, World!" >>>;

new SinOsc ~> dac;

5::second => now;
built with mdr and mdBook
You'll find the source here, Luke! note: privacy guards may interfere with the playground

Declaring a Variable

In the former example, we just created a sinusoidal oscillator, without a way to access it later.

This time, let's declare the oscillator [pronouns are also good to avoid] so it has a usable name, and change some things like volume and frequency

#import Modules

<<< "Hello, World!" >>>;

#! create a Sinusoidal oscillator, named sine
#! and connect it to the speakers
var SinOsc sine ~> dac;

#! let some time pass
1::second => now;

#! change the frequency
220 => sine.freq;

#! let some time pass
1::second => now;

#! change the volume
0.1 => sine.amp;
.5::second => now;

#! change the volume, again
0.6 => sine.amp;
.5::second => now;

#! change the frequency
440 => sine.freq;

#! let some time pass
2::second => now;
built with mdr and mdBook
You'll find the source here, Luke! note: privacy guards may interfere with the playground

Using Functions

The former example raises a question, will we always need to type

some_freq  => sine.freq;
some_duration => now;

everytime we want to set the frequency and advance time?

Functions as mean to encapsulate behavior

Well, no: this can all fit in a function. Let's write it

fun void play(SinOsc sine, const float freq, const dur d) {
  freq => sine.freq;
  d => now;
}

What does this mean?

the fun keyword denotes the start of a function declaration.

the return type of the function follows.
here the function returns nothing, we declare it as void.

After that there is the function name, play in this case.

What follows is the argument list.
A function argument syntax is as follow:

Type name

A simple example function

Let's write a simple adder

fun int add(int a, int b) {
  return a + b;
}

This time the function returns an int, so we change the return type accordingly.

Here is how we call it

fun int add(int a, int b) {
  return a + b;
}
<<< add(1, 2) >>>;

#! alternatively
<<< (2, 3) => add >>>;

A example using functions

#import Modules

fun void play(SinOsc sine, const float freq, const dur d) {
  freq => sine.freq;
  d => now;
}

var SinOsc sine ~> dac;

play(sine, 440, second);
play(sine, 220, .5::second);
play(sine, 220, .5::second);
play(sine, 440, second);
built with mdr and mdBook
You'll find the source here, Luke! note: privacy guards may interfere with the playground

A little psycho-acoustic experiment

Let's now try something that will put in evidence that the distinction between melody and noise is only perceptual.

Look at the snippet (and listen the result)

#import Math
#import Modules

#define NCYCLES 2

1 :=> var int compensate;
second :=> var dur d;
const SinOsc s ~> dac;

while(d >= samp) {
  repeat(NCYCLES * compensate) {
    Math.rand2f(220, 880) => s.freq;
    d => now;
  }
  d / 2. :=> d;
  2 *=> compensate;
}

Here we loop until our base duration is one sample Until that we play NCYCLES * compensate notes of our duration. Notes frequencies are randomly chosen between a low and a high bound (here 220 and 880, aka A3 and A5).

At first we hear some boring slow melody. It then gets faster and faster, up to the point we only hear noise. But if you listen carefully, you'll hear that those noises have different granularities. Near the very end, it's almost like if the noise is whisteling, and the thing is that the frequency at which it whistles is the median between the low and the bounds.

Note: line 4, we use a preprocessor define to avoid magic numbers in our code
(this helps with configurability too)

built with mdr and mdBook
You'll find the source here, Luke! note: privacy guards may interfere with the playground

Basic Argument handling

Let's look at this snippet

#import Std
#import Math
#import Modules

fun int get_ncycles() {
  if(me.args())
    return me.arg(0) => Std.atoi;
  return 2;
}

fun float get_user_duration() {
  if(me.args() > 1)
    return me.arg(1) => Std.atof;
  return 1;
}

fun dur get_duration() {
  return get_user_duration()::second;
}


get_ncycles() :=> const int ncycles;
get_duration() :=> var dur duration;

1 :=> var int compensate;
const SinOsc s ~> dac;

while(duration >= samp) {
  repeat(ncycles * compensate) {
    Math.rand2f(220, 880) :=> s.freq;
    duration => now;
  }
  duration/2. :=> duration;
  2 *=> compensate;
}

Yes, it's almost the same as the previous one, but this one provides argument handling.

For instance this function:

fun int get_ncycles() {
  if(me.args())
    return me.arg(0) => Std.atoi;
  return 2;
}

checks if the file was called with any arguments, using me.args().

If there is at least one argument, we get it as a string, using me.arg(0) since we want the first argument, then we get the int value out of it, with Std.atoi.

If there is no argument, it defaults to 2.

built with mdr and mdBook
You'll find the source here, Luke! note: privacy guards may interfere with the playground