Audio Library

Status
Not open for further replies.
So I've gotten to the point where I need to know if a wav file is still playing.

Yup, you're running into the limitations of the very minimal API I created merely to get it working.

But how can I find out exactly how long a particular wav file is in case I want to begin doing something a second before it finishes playing?

I do plan to add more functions. My general strategy has been to just publish a minimal API and wait for feedback exactly like this. Well, sort-of like this, but maybe giving me a better picture of what you're trying to accomplish?

I do want to add this stuff, for you and for all sorts of other projects. If you can take a few moments to give me a better picture of what you're doing, it would really help me. I like to think about creating APIs in terms of actual projects. Thinking about them from the perspective of underlying tech usually ends up with extremely efficient but difficult to use libraries.


I see that some states list the bit depth and whether the file is stereo or mono, and I assume the sample rate is available somewhere as well, but I don't see were I can get the amount of data after the header to calculate the length, and using the state to calculate it seems like a big kludge anyway because I'd have to wait until it gets into one of those states, seeing as how there are one or two before it gets into that state where it's looking at the header.

Yes, all that stuff is in there. You can writing functions to access it, if you want to get into changing the wav player object.

I'm willing to do this. In fact, I've planned to fill in the API... but I'm waiting for feedback from real projects like yours. I'm interested in understanding project needs and crafting functions that are easy and intuitive, not just raw access to low-level details.

Is the only solution here to look at the wav file parsing code and make my own function to parse the wav file?

Well, if you don't want to wait for me (or someone else) to add it, of course the code is open source and you can hack it any way you like.

And how does your own code handle starting the playback of a second file anyway if you can't access the SD card while a wav file is playing?

The update() functions are called 1 at a time, so they can't interrupt each other.

Or does the existence of AudioNoInterrupts() now mean I can safely access the card for brief periods of time while playing back audio?

Yes, it should be safe to access the SD library if you've used AudioNoInterrupts(). But if you spend too long, you could cause an audio drop out.
 
I do plan to add more functions. My general strategy has been to just publish a minimal API and wait for feedback exactly like this. Well, sort-of like this, but maybe giving me a better picture of what you're trying to accomplish?

I am making a circuit to to control a kinetic art piece. The customer wants the piece to play music, and between each track I need to smoothly transition some RGB leds between colors, and adjust the speed of a motor. I could script it all, but since the music isn't finalized and the customer wants to be able to sell microSd cards with new songs on it to his customers, I'd like it to handle changes automatically.

As I don't want there to be a second of silence between each track, I thought the best way to do it would be to determine how long the song is, and how much time remains, and begin my color fade a second before the song ends.

But if there's no easy way to do that, I guess I'll have to hard code it for now since I need to have the prototype ready tomorrow. I have a more pressing issue at the moment that I need to focus on anyway; that popping noise at the end of each song.

(Since I'm going to script everything, I suppose I can quickly fix that by ending the song before it hits the end of the file.)
 
I do want to add this stuff, for you and for all sorts of other projects. If you can take a few moments to give me a better picture of what you're doing, it would really help me. I like to think about creating APIs in terms of actual projects. Thinking about them from the perspective of underlying tech usually ends up with extremely efficient but difficult to use libraries.

Well, here's another project that I've been working on makes extensive use of sound:

It doesn't use a Teensy; it uses a board of my own design based on the Arduino, but with an ATMega1284, 12 bit DAC, and 3W amplifier on board; but I may eventually transition it over to the Teensy because I'd like to be able to mix multiple sound effects.

Anyway, for that kit I modified the WaveHC lib to allow the automatic looping of a sound file, and though I'd have liked perfectly seamless transitions between sound effects, polling in a loop to see when a sound had finished playing so I could begin the next ended up being good enough in this case.

The sound effects generally fall into one of three categories: head, body, or tail. A firing sound effect might have one sound for the head which transitions into a seamlessly looping body, and then multiple tails based on how long the user was firing. Sometimes however, as in the case of the powerup sound, I have a head that plays first, and a body which I queue up when the head begins to play and then the body automatically begins to loop once the head is finished playing.

(This is all something I would not expect your library to handle as there's too many different things you might want to do. I might want to randomly choose which head to play based on how long you'd previously been firing for example.)

Other times I will have a sound effect like the hum playing and the user will trigger another sound effect, but that sound effect does not exist because they deleted it for whatever reason. In that case I don't want to stop the looping hum if there's no new sound to play but the WaveHC lib doesn't allow for that, so I have to access the SD card to check to see if the file exists before I issue the command to play it. I see your library suffers from the same issue:

Code:
bool AudioPlaySDcardWAV::play(const char *filename)
{
	stop();
	wavfile = SD.open(filename);
	if (!wavfile) return false;

Of course there's no right way to handle this, but if you changed it to only stop the wav if the file exists then the programmer could check to see if the play() failed and stop it himself if that's the behavior he wanted, whereas with this setup I have to halt the audio playback before checking to see if the wav exists...

...or do I? I can call that function at any time to play a new wav file, and it's trying to open the file, and returning failure if it fails to do so, but it's not stopping the audio interrupts when it does this. So is that safe to do without stopping the interrupts? Or should this function be stopping the audio interrupts?


Anyway, the basic things I need are:

1. The ability to seamlessly loop a sound. Right now with the pops in there when it reaches the end of a file, even if manually restarting it turns out to be good enough I wouldn't be able to loop an effect.
2. The ability to seamlessly transition from one sound to another. Again, those pops would get in the way of this.
3. The ability to check to see if a sound exists before I play it in case I don't want the previous sound to stop playing if it doesn't. It seems like I can do this already. And it's less of an issue when I can mix sound effects.

And I think that's pretty much it, for this particular kit anyway. The rest of the stuff I mentioned is stuff I can implement in my own API as long as the basic functionality is there.

This is my sound code. I forgot to mention that when playing a sound I also have the ability to clear any queued sounds, and I have the ability to specify if a sound that is queued will need to loop. And as you can see I modified WaveHC to accept a "loop" parameter, so when it reaches the end of the file in its interrupt it automatically begins the next file so the transition is seamless. This may be necessary when moving from the head to the body in my hum sound effect to avoid any pops, I'm not sure, but when firing the transition is so abrupt I don't need it to be perfectly seamless:

Code:
/*

This function plays a sound file.  
Unless otherwise specified, playing a sound file with play() will clear the queue of pending sounds.
Normally, only updateSound() needs to call play without it clearing the queue.

If the file does not exist, play() will not halt any currently playing sound, or clear the queue.

*/
int play(char* filename, boolean loopsound = false, boolean emptyQueue = true, boolean logerror = false) {
  
  FatReader tmp;
  
  //if (!FatReader::exists(root2, filename)) return 0; // Check to see if file exists before doing anything.
  
    cli(); // Disable interrupts
    if (!tmp.open(root, filename)) { sei(); return 0; } 
    sei(); // Enable interrupts
  
  wave.stop(); // Halt playback of sound currently playing.

  if (emptyQueue) { clearQueue(); } 
 
  if (!file.open(root, filename)) { // "root" and "file" are global variables.
    if (logerror) { Serial1.println(F("Failed to open WAV file!")); halt(4); }
    return 0; // Fail!
  }
    
  if (!wave.create(file)) { 
    if (logerror) { Serial1.println(F("Failed to create WAV object!")); halt(5); } 
    return 0; // Fail!
  }  
    
  setVolume(1.0); // Must be set after call to wave.create()
  wave.play(loopsound);
  
  return 1; // Success!
  
}


/*
This function queues up a file to be played after the current one ends.  It's used when you want to play a sound effect which has a head and a loop.
*/
void queue(char* filename, boolean loopsound = false, boolean logerror = false) {
  
  queuedFile = filename;
  queuedLoop = loopsound;
  
}  


/*
This function begins playing the queued file when the current one ends.
*/
void updateSound() {
  if (!wave.isplaying) {
    if (queuedFile != NULL) { play(queuedFile, queuedLoop); } 
  }
}


/*
This function stops any sounds in the queue from being played back.
*/
void clearQueue() {
   queuedFile = NULL;
}


/*
This function sets the current playback volume.
newVolume = New volume level.  Valid range: [0..1]
Currently, volume must be reset after every new wave file is loaded, but I should probably edit the wave file library to just use a global value.
*/
void setVolume(float newVolume) {
  wave.volume = newVolume * 4095.0;
}
 
I have a more pressing issue at the moment that I need to focus on anyway; that popping noise at the end of each song.

I believe the pop is happening because the last small chunk of the file isn't playing. If the waveform isn't at zero, there's an abrupt transition back to zero.

Try adding a tenth of a second of silence onto the end of your file as a quick fix.

For a demo tomorrow, definitely hard code song lengths. This description really helps. I'll look into improving the library over the next several days.
 
My Current Project

I am currently working on a portable, battery powered tester for use in Land Mobile Radio. Control point "remotes" communicate with base radios over leased twisted pair telephone circuits. The protocol is a high level guard tone (2175Hz for 120ms at 10db) followed by a function tone ( 1950, 1850, 1750........for 40ms at 0db) that sets transmit frequency in the base radio) followed by low level guard tone (2175Hz at -20db). Voice audio is mixed with the low level guard tone and the base radio continues to transmit as long as low level guard tone is detected.

I have basic functionality working for both the transmit and receive functions ( tone generation, mic audio mixing, timing) on tx, on rx (tone detection of high level guard tone, function tone, low level guard tone, notch filtering of low level guard tone from tx audio to base radio).

The transmit audio to the base station will be buffered using a small audio power amp driving a 600 ohm line transformer. Rx audio (after processing) will be feed ( through form C sections on a PTT switch???) to a 4d Systems ULCD-32PTU which will do speaker audio and the UI.

Things I would like to see included in the library:
Level control of the DAC

Sensitivity control of Mic IN ( using Mixer4 object now to get gain control)

A AGC object that could be set like AudioConnection c01(audioIn, 0, myAGC, 0);
AudioConnection c02(myAGC, 0, audioOut, 0);
and expose a method i.e. myAGC.Level( agc level in db?)

Or someway of turning on AGC in the SGTL5000. Which could also handle DAC level control and for that matter some of the filtering.

A level object that would look like AudioConnection c03(audioIn, 0, myLevel, 0);
with a property level_in_db=myLevel.level(), thinking something as simple as sqrt of sum of squares of an input sample block? Some books suggest sqrt of sum of bins squared in an FFT but that seems time consuming.

A frequency object that would do a period based frequency count of a input block. Goertzel is working well for what I need now but I will need higher resolution if I try to do two tone sequential decoding audio range from about 200hz to 2000hz.

I am very pleased with the current library and wish to thank Paul for all his hard work. And if Paul would work up some documentation on the library and some templates I would be happy to try to do some of this myself. Background is in C, I am finding C++ code in the library hard going :)
 
I believe the pop is happening because the last small chunk of the file isn't playing. If the waveform isn't at zero, there's an abrupt transition back to zero.

Try adding a tenth of a second of silence onto the end of your file as a quick fix.

Ah, that's easy enough. I thought the problem was that when it played the final block at the end there was some garbage data. My files do fade out though... I'm skeptical that a second of silence will help, but I'll give it a shot and let you know if it worked.

But even if adding a second of silence does work, wouldn't the following also cause a pop?

Code:
void AudioPlaySDcardWAV::stop(void)
{
	__disable_irq();
	if (state != STATE_STOP) {
		state = STATE_STOP;
		__enable_irq();
		wavfile.close();
	} else {
		__enable_irq();
	}
}

Stopping the file ought to cause just as abrupt a change as cutting the wav file off just before the end.
 
I've attached a sketch I wrote to process stereo samples from the line-in, change them a bit and then output them to the headphones but I get no output at all. I am not familiar with C++ so I've had some fun sorting out how the examples work. I thought I had them figured out but obviously not.
Would some kind soul take a look at my attached sketch and show me what's wrong?

Thanks
Pete
 

Attachments

  • filter_test_a.zip
    1.6 KB · Views: 145
P.S. I forgot to mention that the code does not even start up properly because it doesn't print "A" and "setup done".

Pete
 
...

Things I would like to see included in the library:
Level control of the DAC

...

Or someway of turning on AGC in the SGTL5000. Which could also handle DAC level control and for that matter some of the filtering.

...
AVC appears to be SGTL5000 documented equivalent of AGC. Would SGTL5000 AVC like;
Code:
...
AudioControlSGTL5000 sgtl;
...
void setup()
{
  sgtl.init();
  ... // commands to enable DAP etc etc (AGC is part of DAP in SGTL5000)...
  sgtl.avc(enable,maxGain,lbiResponse,hardLimitEnable,threshold,attack,decay);
}
be any good? Of course with examples and explanations etc etc.

Would DAC level control like;
Code:
void loop()
{
  ...
  sgtl.dacVol((float)n); // n=0 to 100...
  ...
  sgtl.dacVolRight((float)n);
  sgtl.dacVolLeft((float)n);
  ...
}
be ok too?
 
I've found the problem. The beginning of the update function should look like this:
Code:
  l_block = receiveWritable(0);
  r_block = receiveWritable(1);
  
  if(!l_block || !r_block) {
    if(l_block)release(l_block);
    if(r_block)release(r_block);
    return;
  }
  
  lp = l_block->data;
  rp = r_block->data;

Pete
 
AVC appears to be SGTL5000 documented equivalent of AGC. Would SGTL5000 AVC like;
Code:
...
AudioControlSGTL5000 sgtl;
...
void setup()
{
  sgtl.init();
  ... // commands to enable DAP etc etc (AGC is part of DAP in SGTL5000)...
  sgtl.avc(enable,maxGain,lbiResponse,hardLimitEnable,threshold,attack,decay);
}
be any good? Of course with examples and explanations etc etc.

Would DAC level control like;
Code:
void loop()
{
  ...
  sgtl.dacVol((float)n); // n=0 to 100...
  ...
  sgtl.dacVolRight((float)n);
  sgtl.dacVolLeft((float)n);
  ...
}
be ok too?

That would be great. Have you received your teensy 3.1 and audio shield yet?
 
No, but that wouldn't stop me posting you temporary solutions in the next couple of days, if you want 'em quicker...

I found (well, accidentally noticed) an error in the 'filterCalc' routine, part of the last .ino I posted, so I post correction to the error and implementation of (hopefully) even better way of updating the 'live' parameters in the loop();
 

Attachments

  • filterCalc_test_take2.ino
    7.6 KB · Views: 143
Well, I added a second of silence to the end of the audio file, and that seems to have done the trick, so thanks for suggesting that.

I can't say I'm not perplexed by this behavior though. As far as I can tell you're reading 512 samples at a time from the SD card, but you only process 128 samples at a time in the WAV reader, so the most that should ever be thrown away is 128.

Even assuming 512 samples are tossed though, at a sample rate of 44100hz, that's only 1/100th of a second of audio. But the last 3/100th's second of my audio file are silent already.
 
I'm not sure if this is already in there, but a nice feature I just thought of would be the ability to calculate the volume of the audio at a particular point in time. Since a true moving average is probably not possible with only a small part o the waveform in memory at once, I was thinking you could do a weighted average, adding absolute value of the current sample to the previous average, multiplied by some user specified weight, say 99, and then divide the result by that plus one to calculate the new average. A loud section would then not immediately affect the average but if it continued for a while, it would pull it up.

This could be useful for a vu-meter, or for adjusting the brightness of an led, or the strength of a vibration motor to match the loudness of a sound.
 
1. The ability to seamlessly loop a sound.


that sounds like a useful feature. i didn't get round to spending a lot of time on the API, but from what i saw, indeed it won't be possible without coming up with a dedicated function that interleaves the loop. ideally, one would be able to set the loop points (in samples or milliseconds).

Code:
void AudioPlaySDcardWAV::play_loop(uint32_t loop_start, uint32_t loop_end)

one thing i was wondering about -- perhaps at some point it could be extended to adjust the playback speed, assuming the arm chip can pull that off in realtime?
(i've seen el_supremo came up with a resampling sketch, but i figure that's meant to resample the entire file)
 
Last edited:
I'm not sure if this is already in there, but a nice feature I just thought of would be the ability to calculate the volume of the audio at a particular point in time.
......
This could be useful for a vu-meter, or for adjusting the brightness of an led, or the strength of a vibration motor to match the loudness of a sound.

I actually started working on that several days ago, but put it aside for a while after I ran into some troubles with response times and numerical precision. Eventually I'll get this added to the library.

Likewise, I'm considering adding fading and panning objects. Those should really help with applications where you want to have multiple sources with smooth transitions between them.
 
I don't see an example to do an audio recording. With the new audio library, what is the fastest sample rate that can be sampled and written to an sdcard? Are there are any issues with using DMA for ADC, as well as the sdcard?
 
I have a more pressing issue at the moment that I need to focus on anyway; that popping noise at the end of each song.

I think the easiest thing to do is to truncate the audio stream at the last zero-crossing. It will likely be only a few millisecond at most.
 
Ooops I attached this to some powered CPU speakers.... I hope its still good :-(
fwiw I work in the electronics industry and will not, as I have not, hesitate to connect anything with a headphone jack to powered amplifiers using it - I haven't yet seen a fully qualified electronics engineer hesitate to connect power amplifiers to headphone jacks on a variety of devices either.

ipods (blech) & mp3 players (and modern phones etc) would all lose a fair degree of lustre if playing them through amplifiers via their headphone sockets harmed them.

I'd make a point of the volume control being set on the source device to output lower than 'line level' and compensate with the volume control on the powered amp to err on the side of caution but that is about all the caution I would bother with.
 
The headphone output uses a "virtual ground", which is really about 1.5 volts DC. There's a caution message about this on the bottom side of the PCB.

If you connect the headphone output to something that has a floating ground, like amplified computer speakers powered from a wall wart or a battery, it's perfectly fine. Likewise, if Teensy is powered from a battery or floating supply, it's fine too.

Where things can go badly is if Teensy is grounded, which is usually the case with a USB cable connected to a computer running from AC mains power, AND if the amplifier is grounded. Then the 1.5V DC from the headphone VGND connects to GND. The chip has over-current detection and shut down, so the odds of damage are slim. But it won't work, and it does stress the chip slightly, and the current limit is around 100mA which is at about the max current Teensy's built-in 3.3V regulator can provide.
 
No, but that wouldn't stop me posting you temporary solutions in the next couple of days, if you want 'em quicker...

I found (well, accidentally noticed) an error in the 'filterCalc' routine, part of the last .ino I posted, so I post correction to the error and implementation of (hopefully) even better way of updating the 'live' parameters in the loop();

Better, still some radom clicks and you hear clicks when the pot is rotated, but much less noticeable.
 
I don't see an example to do an audio recording. With the new audio library, what is the fastest sample rate that can be sampled and written to an sdcard? Are there are any issues with using DMA for ADC, as well as the sdcard?

Same here, i'm also trying to answer this question.
meanwhile i'm trying to write an audio block to an SD card every ::update, was able to write something (random data, not audio).

Any information regarding SD card file recording will be great :)
 
I believe what Paul is saying is, when using the headphone jack to tie the two together (but not the line out, that's fine), if the Teensy and amplifier are connected to the SAME ground that's bad.

IE:
1. Amplifier connected to same battery as Teensy - BAD
2. Amplifier connected to a separate battery, but with a common ground - BAD
3. Amplifier connected to separate battery, and with no common ground - GOOD
4. Amplifier powered by wall wart, Teensy powered from same wall wart - BAD
5. Amplifier powered by wall wart, Teensy powered by USB with laptop NOT plugged in - GOOD
6. Amplifier powered by wall wart, Teensy powered by USB with laptop plugged in - BAD, if wall wart and laptop both have a ground pin on the plug?
7. Amplifier powered by wall wart, Teensy powered by a second wall wart - BAD, if both wall warts have ground pin on the plugs?

Again note, these only apply to using the headphone jack to tie the two together. Any of the above combos is fine with the Line Out pins.
 
Just a thought, have not tried it, bridge the two grounds with a large(ish) cap and reference everything to GND.
 
Status
Not open for further replies.
Back
Top