Interface

Performance note

Presumably due to the limits of inference, scheduling 16 or more synthesizers simultaneously will lead you off a performance cliff. Hopefully this limitation will go away in future versions of Julia.

AudioSchedules.AudioScheduleMethod
AudioSchedule(; sample_rate = 44100Hz)

Create an empty AudioSchedule. You can push! new synthesizers to the audio_schedule. Provide 4 arguments to push!: the schedule, the synthesizer, the start time, and an envelope that you create with @envelope.

julia> using AudioSchedules

julia> audio_schedule = AudioSchedule()
0.0 s 44100.0 Hz AudioSchedule

julia> push!(audio_schedule, Map(sin, Cycles(440Hz)), 0s, @envelope(
           0,
           Line => 1s,
           0.2,
           Line => 1s,
           0,
       ))

julia> push!(audio_schedule, Map(sin, Cycles(660Hz)), 2s, @envelope(
            0,
            Line => 1s,
            0.2,
            Line => 1s,
            0,
        ))

julia> audio_schedule
4.0 s 44100.0 Hz AudioSchedule

You can iterate over an AudioSchedule. Each element will just be a vector of amplitudes.

julia> length(first(audio_schedule))
44100

julia> collect(AudioSchedule())
Any[]

You can use write the audio_schedule directly to a PortAudio.PortAudioStream. The PortAudioStream must have exactly 1 output channel, and a matching sample rate.

julia> using PortAudio: PortAudioStream


julia> PortAudioStream(0, 1, warn_xruns = false) do stream
           write(stream, audio_schedule)
       end

julia> PortAudioStream(0, 2) do stream
            write(stream, audio_schedule)
        end
ERROR: ArgumentError: PortAudioStream does not have 1 output channel
[...]

julia> PortAudioStream(0, 1, samplerate = 48000) do stream
            write(stream, audio_schedule)
        end
ERROR: ArgumentError: Sample rates of PortAudioStream (48000.0) and AudioSchedule (44100.0) do not match
[...]

You can save an AudioSchedule as a SampledSignals.SampleBuf.

julia> using SampledSignals: SampleBuf


julia> saved = SampleBuf(audio_schedule)
176400-frame, 1-channel SampleBuf{Float64, 1}
4.0s sampled at 44100.0Hz
▃▄▄▄▄▅▅▅▅▅▅▅▅▆▆▆▆▆▆▆▆▆▆▆▆▆▆▅▅▅▅▅▅▅▅▄▄▄▄▃▃▄▄▄▄▅▅▅▅▅▅▅▅▆▆▆▆▆▆▆▆▆▆▆▆▆▆▅▅▅▅▅▅▅▅▄▄▄▄▃

You can empty! an AudioSchedule and reuse it.

julia> empty!(audio_schedule)

julia> audio_schedule
0.0 s 44100.0 Hz AudioSchedule
source
AudioSchedules.CyclesType
Cycles(frequency)

Cycles from 0 to 2π to repeat at a frequency (with frequency units, like Hz). Supports make_series.

julia> using AudioSchedules


julia> using Unitful: Hz


julia> first(make_series(Cycles(440Hz), 44100Hz))
0.06268937721449021
source
AudioSchedules.GrowType
Grow(start_level, duration, end_level)

Exponentially grow or decay from start_level to end_level over a duration in time units like s. Supports make_series.

julia> using AudioSchedules


julia> using Unitful: Hz, s


julia> first(make_series(Grow(0.1, 1s, 1), 44100Hz))
0.10000522141770128
source
AudioSchedules.LineType
Line(start_level, duration, end_level)

A line from start_level to end_level with a duration in time units like s. Supports make_series.

julia> using AudioSchedules


julia> first(make_series(Line(0, 1s, 1), 44100Hz))
2.2675736961451248e-5
source
AudioSchedules.MapType
Map(a_function, synthesizers...)

Map a_function over synthesizers. Supports make_series.

julia> using AudioSchedules


julia> first(make_series(Map(sin, Cycles(440Hz)), 44100Hz))
0.06264832417874369
source
AudioSchedules.SawToothMethod
SawTooth(overtones)

Build a saw-tooth wave from its partials, starting with the fundamental (1), up to overtones.

To increase richness but also buziness, increase overtones.

julia> using AudioSchedules


julia> SawTooth(3)(π / 4)
0.9185207636218614
source
AudioSchedules.ScaleType
function Scale(ratio)

A simple wrapper that will multiply inputs by the ratio.

julia> using AudioSchedules


julia> Scale(3)(2)
6
source
AudioSchedules.durationMethod
duration(audio_schedule::AudioSchedule)

Find the duration of an AudioSchedule in seconds.

julia> using AudioSchedules

julia> audio_schedule = AudioSchedule();

julia> push!(audio_schedule, Map(sin, Cycles(440Hz)), 0s, @envelope(
            0,
            Line => 1s,
            1,
            Line => 1s,
            0,
        ))

julia> duration(audio_schedule)
2.0 s
source
AudioSchedules.make_seriesMethod
make_series(synthesizer, sample_rate)

Return an iterator that will the play the synthesizer at sample_rate (with frequency units, like Hz). The iterator should yield Float64s between -1 and 1. Assumes that iterators will never end while they are scheduled.

source
Base.push!Method
push!(audio_schedule::AudioSchedule, synthesizer, start_time,
    start_level, shape => duration, end_level, more_segments...
)

Add a synthesizer to a AudioSchedule, where synthesizer is anything that supports make_series, start_time has units of time (like s), and the rest of the arguments specify the shape of the envelope.

For all envelope segment, call

segment(shape, start_level, duration, end_level)

duration should have units of time (like s). For example,

push!(audio_schedule, synthesizer, start_time, @envelope(0, Line => 1s, 1, Line => 1s, 0))

will call segment twice:

segment(Line, 0, 1s, 1)
segment(Line, 1, 1s, 0)
source
AudioSchedules.@envelopeMacro
@envelope(arguments...)

Create an envelope. Start with the start amplitude (a number between 0 and 1). Then, specify a pair, segment_function => duration, where duration is the duration of the segment. Then, specify the end amplitude (again, a number between 0 and 1). So for example,

For example, @envelope(0, Line => 1s, 1) will create an envelope with 1 segment. The segment is created with segment_function(start_level, duration, end_level) => duration. So, for this example, the segment will be Line(0, 1s, 1) => 1s.

After you finished your first segment, you can add as many more segments as you'd like. The end level of the previous segment will be the start level of the next segment.

For example, @envelope(0, Line => 1s, 1, Line => 1s, 0) will create an envelope with 2 segments:

  1. Line(0, 1s, 1) => 1s
  2. Line(1, 1s, 0) => 1s
julia> using AudioSchedules

julia> @envelope(0, Line => 1s, 1, Line => 1s, 0)
(Line(0.0, 1.0 s⁻¹) => 1.0 s, Line(1.0, -1.0 s⁻¹) => 1.0 s)

julia> @envelope(1, 2, 3)
ERROR: LoadError: ArgumentError: 2 is not a pair
[...]
source