Interface
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.AudioSchedule
AudioSchedules.Cycles
AudioSchedules.Grow
AudioSchedules.Line
AudioSchedules.Map
AudioSchedules.SawTooth
AudioSchedules.Scale
AudioSchedules.duration
AudioSchedules.make_series
AudioSchedules.@envelope
AudioSchedules.AudioSchedule
— MethodAudioSchedule(; 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
AudioSchedules.Cycles
— TypeCycles(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
AudioSchedules.Grow
— TypeGrow(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
AudioSchedules.Line
— TypeLine(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
AudioSchedules.Map
— TypeMap(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
AudioSchedules.SawTooth
— MethodSawTooth(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
AudioSchedules.Scale
— Typefunction Scale(ratio)
A simple wrapper that will multiply inputs by the ratio.
julia> using AudioSchedules
julia> Scale(3)(2)
6
AudioSchedules.duration
— Methodduration(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
AudioSchedules.make_series
— Methodmake_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 Float64
s between -1 and 1. Assumes that iterators will never end while they are scheduled.
Base.push!
— Methodpush!(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)
AudioSchedules.@envelope
— Macro@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:
Line(0, 1s, 1) => 1s
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
[...]