Saving Audio with Correct Sample Rate

Hay!

I’m trying to generate and save a very simple 440Hz waveform.

Unfortunately, the generated file does not inherit the 44100Hz sample rate that I assigned.
Instead it has a 8000Hz sample rate:

$ file test.wav
test.wav: RIFF (little-endian) data, WAVE audio, mono 8000 Hz

Here’s the code that I am using:

using SampledSignals
using FileIO
using LibSndFile

sr = 44100 # sample rate
signal = sin.(2π*440*x for x in 0:1/sr:1)
signal = SampleBuf(signal, sr)
@assert samplerate(signal) == sr

save("test.wav", signal)

Could this be an error with the LibSndFile package that I am using?

Kind regards,
ambiso

It works with WAV.jl:

using SampledSignals
using FileIO
using WAV

sr = 44100
signal = sin.(2π*440*x for x in 0:1/sr:1)
signal = SampleBuf(signal, sr)
@assert samplerate(signal) == sr

wavwrite(signal, "test.wav", Fs=samplerate(signal))

I suspect maybe it’s actually saving with WAV.jl, not LibSndFile. If you uninstall WAV.jl the first example should work.

The issue basically is that FileIO.jl has WAV.jl as the first priority package when working with WAV files, to it uses it if it’s installed.

That does indeed work!

Why does it matter whether I have WAV installed?
Shouldn’t it only change the behavior if i actually import it?

FileIO has a list internally of which packages should be used to load which audio types, so it will import WAV.jl automatically if it’s installed. LibSndFile isn’t registered in the FileIO list - it adds itself to the list when you import it. Unfortunately LibSndFile gets added lower-priority than WAV.jl, so WAV.jl still takes precedence if it’s installed. I think a long time ago the loading worked slightly differently so more-recently-added handlers took priority, but that changed and I haven’t taken the time to figure out how to adapt LibSndFile. (or maybe I’m misremembering and it never worked…). If someone wants to look into it I would gladly take a PR.

One thing that’s tricky is making sure it works when multiple packages are trying use load. If one of them is expecting WAV.jl to be used and then another package overrides the list, things could break.

At some point (probably this summer) I’m planning on re-vamping the audio packages so that the low-level packages like LibSndFile and PortAudio work in terms of base Julia types rather than the fancier SampledSignals wrappers, and move the fancy stuff into a metapackage. Then it wouldn’t matter so much whether FileIO used LibSndFile or WAV, because they would both return a regular Array (or an (::Array, ::Float64) tuple with the data and samplerate). The downside is that the default FileIO experience would be less slick, and there would be some different function in the metapackage like audioload or something that would return the samplerate-aware type.