Audio Library

Status
Not open for further replies.
I've just committed a change to add an AudioSynthWaveform object, which can synthesize any arbitrary waveform. The library defines 4 waveforms: sine, square, triangle and sawtooth. This replaces AudioSineWave, which could only do sine waves.

You can use the object like this:

Code:
AudioSynthWaveform  myTone(AudioWaveformSine);

This creates an object with 1 output, which you can feed into mixers, directly to the I2S or PWM, or any other object that accepts input.

The parameter "AudioWaveformSine" is an array of 257 integers. The waveform is 256 integers long, plus a duplicate of the first value. This 257th number is needed because AudioSynthWaveform uses DDS with linear interpolation between samples. You can pass any array of 257 int16_t's, representing 1 period of the waveform, and AudioWaveformSine will synthesize the waveform at any frequency. Sub-audible frequencies are possible, so this can be used to create control waveforms... for a number of interesting objects that don't exist yet, but they're coming soon.

These objects have 2 functions you can call, amplitude(value) and frequency(value). The amplitude expects a float between 0 to 1.0, and frequency expects a number between 0 to 22050.
 
I just checked the waveforms on a scope, and yes indeed, there are problems at higher frequency. Visually, it looks like the amplitude is somewhat unstable, but if I get the triggering just right, it's clearly some low frequency content superimposed on the waveform. :(

Looks like I have quite a bit more work to do on this......

Edit: Maybe instead of a fixed table, these objects will need to take a reference to an object that's a collection of tables to be used in different frequency ranges.
 
I just checked the waveforms on a scope, and yes indeed, there are problems at higher frequency. Visually, it looks like the amplitude is somewhat unstable, but if I get the triggering just right, it's clearly some low frequency content superimposed on the waveform. :(

Looks like I have quite a bit more work to do on this......

Edit: Maybe instead of a fixed table, these objects will need to take a reference to an object that's a collection of tables to be used in different frequency ranges.

yep, this question came up on the old audio thread. i was wondering too whether, say, a bandlimited saw or pulse is worth the trouble when the purpose of AudioSynthWaveform is to do arbitrary waveforms? so some reduced harmonics/wavetable-per-octave scheme seems more practical/less complicated?
 
.
Edit: Maybe instead of a fixed table, these objects will need to take a reference to an object that's a collection of tables to be used in different frequency ranges.

That is the usual way to handle this, for example one table per octave (and is probably easier than taking one largest table and then computing the bandlimited subtables in the setup function). The tables become progressively smaller for higher frequencies so the total space needed for table storage is around twice that of a single, non-bandlimited table.

While this improves the aliasing, there is still a discontinuity on a frequency sweep on passing from one table to the next. The next refinement is therefore to interpolate between a pair of tables (taking progressively more from the 'upper octave' table as the note moves through the octave).
 
Some additional resources that may be useful:
* 'Adventure Kid' single-cycle waveforms. These use a CC-BY-3.0 (Creative Commons) license.
* Cross-platform morphing Wavetable maker (zip file) requires SciLab to run.
* Band-Limited Sound Synthesis (lecture notes and sample C++ code)
* Stilson & Smith Alias-Free Digital Synthesis of Classic Analog Waveforms (pdf)
* Bandlimited Synthesis (lecture notes from McGill University)
The tablemaker is intended for the Mungo g0 wavetable oscillator but saves the morphed wavetable as a mono 16bit .wav file. The lecture notes are specific to bandlimited square wave synthesis, and seem to advocate a buffer of sample differences rather than a buffer of samples. Stilson & Smith argue for bandlimited impulse trains (BLIT) which is also where the McGill lecture notes end up.
 
Some additional resources that may be useful:

* Cross-platform morphing Wavetable maker (zip file) requires SciLab to run.

what's the mungo scilab thing outputting? just "morphing" wavetables? or bandlimited ones?

at any rate, if this was going down that route, some "mip map" wavetable maker would come in handy. fwiw, when playing around with the old dma/i2s library, i created wavetables using audioterm http://dl.dropbox.com/s/1y0d88ts8zy1um7/Audio-Term.zip, which lets you edit+export single cycle wave forms as a .wav and then ran a processing sketch to generate the band-limited versions. audio-term is windows only unfortunately, but runs ok in wine.
 
mxxx I don't know, I stopped at the 'will need to install SciLab, do that later' step. I don't actually have any of his modules though I hear they are good.
Got a link to the processing sketch that does band-limiting? (Presumably it makes a continuous version then re-samples?)
 
I have been looking a the doc trying to figure out the best way to mute an input and not having much luck. Will the mixer at some point have a gain control?
 
I just checked the waveforms on a scope, and yes indeed, there are problems at higher frequency. Visually, it looks like the amplitude is somewhat unstable, but if I get the triggering just right, it's clearly some low frequency content superimposed on the waveform

What is happening is high frequencies harmonics are being folded down in the wrong place in the spectrum. If your scope has an FFT mode it will guide an intuition about this.

The "arbitrary waveform generator" is never quite in reach. We always have bandwidth and noise limitations. A true sawtooth and square wave alas
are impossible to make.
 
mxxx I don't know, I stopped at the 'will need to install SciLab, do that later' step. I don't actually have any of his modules though I hear they are good.
Got a link to the processing sketch that does band-limiting? (Presumably it makes a continuous version then re-samples?)

i see. neither do i (for obvious reasons. not that i would mind owning a bunch), so i couldn't be bothered either. as to the sketch, i can pm it, it's just something i hacked up though. it takes a single cycle .wav runs an fft, removes the upper partials, then ifft. and so on, recursively.
 
AudioInputAnalog not update

Hi Paul,

I tried your instruction on creating my own audio stream worker object but for some reason, I'm not getting any update call in the audio stream. Attached is my code. Right now it doesn't do much. I just want to see if I can get some audio sample data.

Any help is greatly appreciated.
Tomsim
View attachment getAudio.ino
 
Opps, looks like there's a bug in AudioInputAnalog. All input and output objects are supposed cause the updates. To be honest, I've never tested AudioInputAnalog alone... every test I've done so far used it together with at least one other input or output object, so I never caught this issue.

I'll work on a fix soon.

In the meantime, a simple workaround is to just add an unused PWM output object, like this:

Code:
AudioOutputPWM   pwmOutput;

You don't need to make any connection to it. Just having it will cause updates to happen as they should.

I'll fix AudioInputAnalog, but at the moment I'm working on converting WAV and playing clips from flash, so it might be a few days.

Also, you might want to make outputflag volatile. It may not matter now, but it's a possible issue (and something future docs ought to mention) since the updates occur at low priority interrupt context.
 
The "arbitrary waveform generator" is never quite in reach. We always have bandwidth and noise limitations. A true sawtooth and square wave alas
are impossible to make.

True, in theory they require infinite bandwidth up to radio frequencies. In practice, "very good" and "indistinguishable for all practical purposes from mathematically ideal" square, triangle and saw are perfectly possible when limited to the audio bandwidth.
 
it takes a single cycle .wav runs an fft, removes the upper partials, then ifft. and so on, recursively.
I'm wondering, ARM math has fft does it have ifft too? Maybe possible to do that processing on the teensy itself (not in real time, but it could write the results to flash or SD for later use).
 
What's the best way to convert from a lower sample rate to 44.1 kHz? Or what's the fastest, or best trade-off?

I'm working on playing slower sample rate WAV files, like 8 kHz sample rate. Right now, I'm working on simply playing some samples 5 times, others 6 times, to at least get _something_ working.

Surely there must be better ways. A couple quick searches have turned up a lot of stuff about fairly complex filtering....
 
as to resampling, afaik, this is popular and fixed point*: http://www.mega-nerd.com/SRC/index.html

and, though i know that's not exactly what you're after, there's a few pd objects which can deal with arbitrary sample rates and have some interpolations going on (personally, i was wondering whether the AudioPlaySDcardWAV couldn't be expanded in the direction)

tabread4~ https://github.com/soundcloud/pd-zexy/blob/master/src/tabread4~~.c

susloop~
https://github.com/pd-l2ork/pd/blob/master/externals/bsaylor/susloop~.c

xgroove~
https://github.com/pd-l2ork/pd/blob/master/externals/grill/trunk/xsample/source/groove.cpp

*edit: no it's not. resample_1.8 is, though: https://github.com/S25RTTR-Aux/s25rttr/tree/master/s-c/resample-1.8.1
 
Last edited:
What's the best way to convert from a lower sample rate to 44.1 kHz? Or what's the fastest, or best trade-off?

I'm working on playing slower sample rate WAV files, like 8 kHz sample rate. Right now, I'm working on simply playing some samples 5 times, others 6 times, to at least get _something_ working.

Surely there must be better ways. A couple quick searches have turned up a lot of stuff about fairly complex filtering....


I would go with linear filtering. Nearest neighbor as you are suggesting is unlikely to sound good.

Tween = (DestSample / 44khz) * SourceSampleRate

So for a 22khz file, sample 0 of the destination would be sample 0 of the source. Sample 1 would be sample 0.5. Sample 2 would be sample 1. Sample 3, sample 1.5...

If you can do this with fixed point math then you should be able to toss out the integral part of the number and keep the fraction easily and then use that to interpolate between samples 0 and 1 of the source to calculate sample 1 in the destination.

Ie:

A = Source sample 0
B = Source sample 1
DestSample = (B-A)*TweenFraction

This won't work if you want to scale something down from above 44khz of course. Then you'd have to average multiple samples together, weighting them if some are partially outside the window the destination sample covers.

(There's probably a better way to do the math above that doesn't need to store the integral part of your result. Perhaps you can take advantage of overflow to leave behind only the last 8 or 16 bits of the result and use that to do your calculation.)
 
Last edited:
What's the best way to convert from a lower sample rate to 44.1 kHz? Or what's the fastest, or best trade-off?

I'm working on playing slower sample rate WAV files, like 8 kHz sample rate. Right now, I'm working on simply playing some samples 5 times, others 6 times, to at least get _something_ working.

That will work, but badly. Its the audio equivalent of nearest-neighbour interpolation for images, so gives ugly blocky results with increased noise and with aliasing caused by the non-bandlimited, regularly-spaced square steps you are introducing.

Simple linear interpolation will give much better results. Not correct, since the peicewise linear representation has sharp transitions where the edges join, but way better than just repeating samples.

Surely there must be better ways. A couple quick searches have turned up a lot of stuff about fairly complex filtering....
Yes, the proper way resamples it to the analog domain as a continuous waveform then resamples it.
 
I'm trying for linear interpolation today, pretty much like this at the moment.

Code:
        } else if (rate == 22050) {
                int16_t s0, s1, s2, s3, s4;
                s0 = prior;
                if (bits == 8) {
                        for (i=0; i < AUDIO_BLOCK_SAMPLES; i += 8) {
                                tmp32 = *in++;
                                s1 = (int8_t)((tmp32 >> 0) & 255) << 8;
                                s2 = (int8_t)((tmp32 >> 8) & 255) << 8;
                                s3 = (int8_t)((tmp32 >> 16) & 255) << 8;
                                s4 = (int8_t)((tmp32 >> 24) & 255) << 8;
                                *out++ = (s0 + s1) / 2;
                                *out++ = s1;
                                *out++ = (s1 + s2) / 2;
                                *out++ = s2;
                                *out++ = (s2 + s3) / 2;
                                *out++ = s3;
                                *out++ = (s3 + s4) / 2;
                                *out++ = s4;
                                s0 = s4;
                        }

This approach requires keeping only 1 prior sample, but 2 or 3 prior samples could be kept. Those 8 output lines could become simple polynomials implementing a short FIR filter. But what equations to use? Adrian, any suggestions?

I'm also still open to any suggestions for playing 8 kHz sampled files....
 
You're creating quite the audio library :) I would be happy already with only the capability to read samples from flash, audio in and pushing samples out... if you get into the territory of synthesis using BLIT's, SINC-interpolation and filters you could be diving into a new life's work :)

As for upsampling from low sample rates, a simple linear interpolation would suffice (very similar as you would need in a wavetable). The lower the sample rate of the source file, the less blocks you would need available. It does mean that when using linear interpolation samples repeat, but they slowly 'fade' from the previous to the next. I've never thought about it in the context of low-memory devices and arbitrarily large sound files, but I think it would mean that it would be beneficial for the the routine fetching blocks from disk to know the sample rate of the material.

Cheers! My audio board should arrive tomorrow... very exciting! :)
 
Status
Not open for further replies.
Back
Top