24acc0247b
* Add octave shift example * specify duration * use for loop
89 lines
2.8 KiB
Julia
89 lines
2.8 KiB
Julia
#=
|
|
This code illustrates real-time octave down shift
|
|
using a crude FFT-based method.
|
|
It also plots the input and output signals and their spectra.
|
|
|
|
This code uses the system defaults for the audio input and output devices.
|
|
If you use the built-in speakers and built-in microphone,
|
|
you will likely get undesirable audio feedback.
|
|
|
|
It works "best" if you play the audio output through headphones
|
|
so that the output does not feed back into the input.
|
|
|
|
The spectrum plotting came from the example in
|
|
https://github.com/JuliaAudio/PortAudio.jl/blob/master/examples
|
|
=#
|
|
|
|
using PortAudio: PortAudioStream
|
|
using SampledSignals: Hz, domain
|
|
using SampledSignals: (..) # see EllipsisNotation.jl and IntervalSets.jl
|
|
using FFTW: fft, ifft
|
|
using Plots: plot, gui, default; default(label="")
|
|
|
|
|
|
function pitch_halver(x) # decrease pitch by one octave via FFT
|
|
N = length(x)
|
|
mod(N,2) == 0 || throw("N must be multiple of 2")
|
|
F = fft(x) # original spectrum
|
|
Fnew = [F[1:N÷2]; zeros(N+1); F[(N÷2+2):N]]
|
|
out = 2 * real(ifft(Fnew))[1:N]
|
|
out.samplerate /= 2 # trick!
|
|
return out
|
|
end
|
|
|
|
|
|
# Plot input and output signals and their spectra.
|
|
# Quantize the vertical axis limits to reduce plot jitter.
|
|
function plotter(buf, out, N, fmin, fmax, fs; quant::Number = 0.1)
|
|
bmax = quant * ceil(maximum(abs, buf) / quant)
|
|
xticks = [1, N]; ylims = (-1,1) .* bmax; yticks = (-1:1)*bmax
|
|
p1 = plot(buf; xticks, ylims, yticks, title="input")
|
|
p3 = plot(out; xticks, ylims, yticks, title="output")
|
|
|
|
X = (2/N) * abs.(fft(buf)[fmin..fmax]) # spectrum
|
|
Xmax = quant * ceil(maximum(X) / quant)
|
|
xlims = (fs[1], fs[end]); ylims = (0, Xmax); yticks = [0,Xmax]
|
|
p2 = plot(fs, X; xlims, ylims, yticks)
|
|
|
|
Y = (2/N) * abs.(fft(out)[fmin..fmax])
|
|
p4 = plot(fs, Y; xlims, ylims, yticks)
|
|
|
|
plot(p1, p2, p3, p4)
|
|
end
|
|
|
|
|
|
"""
|
|
octave_shift(seconds; N, ...)
|
|
|
|
Shift audio down by one octave.
|
|
|
|
# Input
|
|
* `seconds` : how long to run in seconds; defaults to 300 (5 minutes)
|
|
|
|
# Options
|
|
* `N` : buffer size; default 1024 samples
|
|
* `fmin`,`fmax` : range of frequencies to display; default 0Hz to 4000Hz
|
|
"""
|
|
function octave_shift(
|
|
seconds::Number = 300;
|
|
N::Int = 1024,
|
|
fmin::Number = 0Hz,
|
|
fmax::Number = 4000Hz,
|
|
# undocumented options below here that are unlikely to be modified
|
|
in_stream = PortAudioStream(1, 0), # default input device
|
|
out_stream = PortAudioStream(0, 1), # default output device
|
|
buf::AbstractArray = read(in_stream, N), # warm-up
|
|
fs = Float32[float(f) for f in domain(fft(buf)[fmin..fmax])],
|
|
Niters::Int = ceil(Int, seconds * in_stream.sample_rate / N),
|
|
)
|
|
|
|
for _ in 1:Niters
|
|
read!(in_stream, buf)
|
|
out = pitch_halver(buf) # decrease pitch by one octave
|
|
write(out_stream, out)
|
|
plotter(buf, out, N, fmin, fmax, fs); gui()
|
|
end
|
|
nothing
|
|
end
|
|
|
|
octave_shift(5)
|