For music
A "Gen" has a method to implement which has the signature -
proc(::Gen, ::Bus, t::Float64) -> Tuple{Float64,Gen}
The proc method is expected to schedule any signals to the bus using sched(::Bus,t,::Signal) and return info about the next gen to trigger later. The t argument to proc is "clock time" and not real time - i.e. it is determined by the clock which the bus runs.
Missing docstring for Synth.Gen. Check Documenter's build log for details.
Synth.seq — Functionseq(ga :: GA, gb :: GB) :: Gen where {GA <: Gen, GB <: Gen}Sequences the given two gens to occur one after the other. When the first gen ga results in a Cont, it will switch to gb and after that will continue onwards.
Synth.track — Functiontrack(gs :: AbstractVector) :: GenA "track" is a heterogeneous sequence of Gens. When each gen finishes, its proc call will produce a Cont which indicates it is time to switch to the next Gen in the track. This is like a generalization of seq except that seq has types known at construction time.
Synth.durn — Functiondurn(d :: Real, gen :: Gen) :: GenForces the duration of the given gen to be the given d, even if it was something else. Possibly useful with chord.
Synth.chord — Functionchord(gens :: AbstractVector) :: GenA Gen which runs all the given heterogeneous gens in lock step. The duration of the chord is given by the maximum of the durations of all the gens every time they run. Whenever a gen produces a Cont or Stop, it is taken out of the set of gens and the other continue on.
Broadly, chord is useful when all the gens have the same duration - very much like a chord in music. If you're thinking of whether you should use this to start off multiple musical processes in parallel with each determining its own duration, you probably want par.
Synth.par — Functionpar(gens :: AbstractVector) :: ParFor parallel composition of gens given as a vector. The Par itself has effectively zero duration and when sequenced as part of a track will result in the following gen being immediately processed without delay. The individual gens part of each of the "threads" will continue to be processed by the bus over time until they finish.
Synth.loop — Functionloop(n :: Int, g :: Gen) :: GenWill loop the given gen n times before ending.
Synth.dyn — Functiondyn(fn :: Function, n :: Int, i :: Int) :: GenA "dyn" gen calls the function with each i to determine the gen that should be performed. The function will be called with two arguments i and n where i will range from 1 to n.
Synth.rec — Functionrec(fn :: Function, c :: C, s :: S) :: Gen where {C,S}Constructs a recursive process using the given function. The function takes the current state and is expected to return a tuple of a Gen and the next state. If the returned gen is Cont, then the gen is considered finished and will move on to the next gen in whatever context it was invoked.
Iterating an octave
For example, if you want a gen which iterates through the pitches of an octave you can do it this way (though you can accomplish it using track as well).
function octave(p :: Real, i :: Int)
if i <= 12
(tone(p + i, 0.25), i+1)
else
(Cont(), i+1)
end
end
b = bus()
sched(b, rec(octave, 60, 0::Int))
play(b, 4.0)rec is therefore a general mechanism to implement stateful recursive time evolution at a level higher than signals. Note that the function called by rec can itself return another recursive process as a part of its evolution.
Synth.ping — Functionping(pitch :: Real, dur :: Real, vel :: Real = 0.5f0, decay :: Real = dur) :: Gen
ping(pitch :: AbstractVector{R}, dur :: Real, vel :: Real = 0.5f0, decay :: Real = dur) :: Gen where {R <: Real}
ping(pitch :: AbstractVector{R}, dur :: AbstractVector{RD}, vel :: Real = 0.5f0, decay :: Real = dur) :: Gen where {R <: Real, RD <: Real}
ping(pch :: PitchChord, dur :: Real, vel :: Real = 0.5f0, decay :: Real = dur) :: GenA "ping" is a simple decaying sine tone for illustrating how to create a Gen. If you use the array versions, they're made into corresponding tracks for ease of use. So ping([60,67,72], 0.5) would be a three-note track. If the duration is also an array, it will be cycled through for each of the pitch values. So the number of pings is determined only by the number of pitches given.
Synth.tone — Functiontone(pitch :: Real, dur :: Real, vel :: Real = 0.5f0; release_secs :: Real = 0.05) :: Gen
tone(pitch :: AbstractVector{R}, dur :: Real, vel :: Real = 0.5f0; release_secs :: Real = 0.05) :: Gen where {R <: Real}
tone(pitch :: AbstractVector{R}, dur :: AbstractVector{RD}, vel :: Real = 0.5f0; release_secs :: Real = 0.05) :: Gen where {R <: Real, RD <: Real}
tone(pch :: PitchChord, dur :: Real, vel :: Real = 0.5f0, release_secs :: Real = 0.05) :: Gentone is related to ping in that it will sustain a tone for the given duration as opposed to ping which will immediately start releasing the tone. In other words, a ping is a note with zero duration. However, the note is configured with a default short release time where the default release of a ping is determined by its duration.
Note that you can force the duration of a gen to be whatever you want using durn.
tone(wt :: WaveTone, pitch, dur, vel = 0.5f0; release_secs = 0.05)With this, you can take an existing configured wavetable tone and assign a different pitch/dur/vel characteristic to it.
Synth.ch — Functionch(pitches :: AbstractVector{R}) :: PitchChord where {R <: Real}Represents a set of chorded pitch values. Usable with ping.
Missing docstring for Synth.wavetone. Check Documenter's build log for details.
Synth.snippet — Functionsnippet(filename::AbstractString, selstart :: Float64 = 0.0, selend :: Float64 = Inf) :: GenA Gen that plays a fragment of the given audio file. It uses sample under the hood and therefore relies on its caching mechanism for speedy schedule of sample fragment playback.
Note that both snippet and sample do not support resampling or playing back to clocks that vary their speeds from real time. So you need to be careful with duration computation. For example, when scheduling on to a bus running at a tempo of 120bpm, you'll need to double your durations using durn in order to synchronize with the end of the snippet. Otherwise, the next Gen will start playing half way through the snippet.