// - basics // - c style comments // - one expression per line // - syntactic whitespace for block scoping // // -variables // - all variables are arrays. // // - types are inferred from context // value = 1.0 will create a float array of size 1 // value = [1.0, 2.0, 3.0] will create a float array of size 3 // value = cube() will create a shape // value = osc("saw", [40hz, 80hz]) will return 2 audio buffers // // - variables will be resized as required, no need to define sizes up front. // channels[0] = left_channel_output // channels[1] = right_channel_output // return channels // // - if variables of different sizes get combined together, the resulting size is minimum of the two. // [10, 20, 30] + [5, 5] --> [15, 25]. // // - if one of the arrays is size 1, then that value will be combined with all the elements of the other. // [10, 20, 30] + [5] --> [15, 25, 35] // // - the same behaviour applies to function inputs // osc("sin", [freq0, freq1, freq2, freq3], [amp0, amp1]) --> [ osc("sin", freq0, amp0), osc("sin", freq1, amp1) ] // // - some variable types have fields that can be accessed and modified // p = point(0, 1) // p.x += 4 // // - fields are themselves arrays, so the following increases the frequency of all notes // notes = midi() // notes.hz += 200hz // // - some constant value variables are provided by the application and are available in most // contexts. they are denoted with a leading $ character (eg $time). the full list of constants is // provided in the documentation. // // -functions // - not all functions are available in all contexts. see documentation for more details on whats available where // - the three entry points for the synth are audio(), scene() and post(). they are all optional // //this is the audio thread entry point. it requires audio buffers to be returned. audio() //calls to midi() with no device name return the state of the keyboard. in this //example, pressing q or w while in 'midi' mode (press shift+m while not in insert mode) //will produce on/off signals for those keys notes = midi(["q", "w"]) //this signals can be used to trigger audio envelopes. the adsr will return separate signals for both notes. //this can be seen by placing the text cursor over 'amp' or 'adsr' on the following line and using //the 'viewer' mode (press shift+v while not in insert mode) amp = adsr(40ms, 100ms, 0.8, 200ms, notes.signal) //exporting the amplitude values so that scene() can access them export(amp) //using the adsr outputs to scale an oscillator. again, this will return two signals. //there are two output signals here so they will end up in the left and right //channels of a stereo setup. midi notes have frequencies associated with them. these //are defined in the settings.txt file output = amp * osc("sin", notes.hz) //compressor should prevent audio clipping return compressor(output) //this is the geometry entry point. the program handles multiple scenes by specifying a //scene index inside the draw() functions. //nothing should be returned from the scene() function scene() //importing values from the audio thread amp = import("amp") //reading the first sample from each input. these will be used as scaling values for shapes sx = 2 + 2 * amp[0][0] sy = 2 + 2 * amp[1][0] //defining user functions and calling them is a thing shape = generate_shape(sx, sy) //conditionals exist. ui buttons and stuff also exist and respond to mouse inputs and audio signals. //integers, floats and audio can all be passed to conditional statements. if checkbox() offsets = [0.0, 1.0, 2.0, 3.0] //loops are defined by a iterator name and values to loop between. these values are inclusive loop i from 0 to 8 //array reads will wrap the index to the bounds of the array. the offsets array here is smaller than the loop. xform = translate(3 * (i - 3), offsets[i], 0) draw(xform * cylinder(0.4*sx, sy)) else draw(shape) //this can be omitted in the scene() function but its in this example script for clarity return generate_shape(scale_x, scale_y) //there are a few default primitive shapes (cubes, cylinders, cones, pryramids, spheres and torii) //hexcodes for colours are supported left = cube(scale_x, #f00) right = sphere(scale_x, #0000ff) //transformations can be applied to them to move them around xform = translate_x(scale_y + 4) xform = rotate_z($time) * xform //shapes are distance fields by default so they can be combined in strange ways return union(left, xform * right, 6 + sin($time)) //this is the post effects chain. the idea is to take output from scene() and pass it //to various shader functions and then return a texture to be displayed. //the output from scene() can be found in the constant $scene which is an array of textures. post() //importing stuff in the post() thread is currently the only way to pass audio parameters //into shaders. amount = 0.1 * import("amp")[0][0] //shaders get called by name. $scene is the output texture from the scene() function. by default //there are two scenes, so we pass through the one we drew shapes into return shader("redshift", $scene[0], amount) redshift(texture, amount) //there are a bunch of constants provided to shaders for sampling and whatever c = sample(texture, $uv) red = sample(texture, $uv.x + amount, $uv.y) //all shaders need to return a colour return colour(red.r, c.g, c.b, 1.0)