Hi all.
So, I'm trying to build an audio recording and playback device using Teensy 3.0. I'm pretty new at this stuff--I've been coding for years, but the hardware stuff still kind of feels like voodoo to me, so please bear with me if I make bizarre choices or silly mistakes. Arduino doesn't really seem fast enough for proper audio, but with Teensy's 32-bit processor, higher clock speed, and high-quality ADC, it seemed like audio might be within reach. This is for my sister, who does some fancy stuff with sampling and tracking for her music performance, but would like something she can use live at a venue. The input would be a microphone, probably either XLR or 1/4" jack; the output would be 1/4" jack line out to a PA system. Since it's just one mic input, monoaural is fine, but she'd like the audio to be fairly high quality.
Now, the Teensy ADC, being a successive approximation sampler, may not quite be suited to this, so I'm prepared to add an external ADC if necessary. I picked up one with a breakout board from sparkfun so I'll have it available if I need it.
Unfortunately, I haven't gotten as far as input yet...I'm having trouble already on output. I was hoping to achieve 44.1kHz 16-bit mono output. But samples for that take up quite a lot of space--the Teensy's onboard RAM holds less than a fifth of a second at that rate. So I loaded the sample (a copy of "Green and Blue" by Miles Davis, off "Kind of Blue", downmixed to mono) as a WAV file on a microSD card. Since the target design involves being able to record and play back from up to four different sample "slots," I named the file "1_44kHz.wav"--1 indicating the slot number, and 44kHz indicating the playback rate. I copied over a couple of reduced-sample-rate copies (22 & 11kHz, respectively) for additional testing.
Because I still need to do so much experimentation to get things working these days, I used the PCM56P audio chip from TI as my DAC; it's a 16-bit Burr-Brown DAC, capable of running at tremendous sample rates, with a convenient serial interface and an even more convenient 16-pin DIP package, so I can use it on a breadboard. The timing turned out to be fairly forgiving, and it's able to keep up as fast as I can bitbang the interface with digitalWriteFast(). (I was a little worried about this, because it says the max clock is 10MHz, but apparently I'm wasting enough clock cycles, even at a 96MHz clock, that the DAC has no trouble with it, and the 'volatile asm("nop\n nop\n")' sort of stuff I had in macros was unnecessary.) It required +5 and -5 voltage lines for input, which was a bit of a pain, but I had an ancient ATX 1.0 computer power supply sitting on a shelf, and ran some lines from that, which worked fine, though it's a bit bulky. I'll work out a more svelte power supply once I've gotten the kinks out of the workings.
For the sketch, I used the programmable interrupt timer (which required a bit of fiddling--not sure if they've sorted that in a newer beta of the code yet; I'm still using the system I described in an earlier post, "Teensy 3.0 and interrupts") to generate interrupts at 44.1kHz, and played back from a sample buffer. When the buffer runs out, I load a new set of samples from the microSD.
The trouble with this is that the microSD takes a while to read when it's a new access. The byte transfer rate is no problem, but the read latency is. I haven't figured out exactly how long the read takes; it's less than a millisecond (I used "millis()" to check for slow reads and output a message to Serial), but it's audible even at 11kHz, which means it's more than about 180 microseconds. Interestingly, the effect is quite subtle until the trumpet kicks in; it's loud and high-frequency, so that makes some sense, but it's striking how much more noticeable it is than with just the bass and piano.
I tried adding a "deglitcher," or sample-and-hold amplifier, but that didn't do much...which makes sense, since the problem is that it *stops* varying, not that it's varying when it shouldn't. A deglitcher is really only called for with this DAC when you're using it for stereo output--then you can use two deglitchers and an inverter to multiplex the output. But it seemed worth a try, anyway.
So I'd love advice and input on the project. I'm not exactly stalled--there are things I can think of to try with the project. The first is looking more at the documentation for the sdfat library; it's possible there are better ways to do sequential reads, or perhaps I can do something to align the reads with the block boundaries. I'm not overly hopeful on that front, though, since I'm currently using the standard read(buffer, num_bytes) format, and the buffer is quite large by Teensy standards, so the seek costs should be somewhat amortized there. I've also tried using a much smaller buffer (512B), to match the block size on the microsd, with much more frequent reads, but that just increases the *number* of latency waits, which is no good. It just makes the whole sample sound slowed-down and wobbly. Another possibility is setting up some SRAM chips to handle the buffering; that adds a fair amount of complexity, and the chips aren't necessarily huge anyway, but it'd definitely speed up memory access, and if the glitches were rare enough, they'd probably be fine. But this seems like the sort of thing other people have probably done before, and done better--is there a completely different approach that would work much better? Perhaps it'd be better to have an external SRAM chip, but also an external counter and latch and timer, so that the SRAM could feed directly in to the DAC, and the Teensy could just sneak in between those reads and burst-transfer data from the microSD to the SRAM. Or something along those lines. Anyone have thoughts on this, or on good approaches to audio in general?
I know there have been a lot of audio shields for Arduino in the past, but it seems like they're all either strictly mp3-playback-from-microsd-with-minimal-arduino-interaction, which doesn't really seem suited (not least because I don't want to try to implement mp3 audio compression on the teensy to store recorded stuff) or else 12-bit recording-and-playback shields. Or, in one case, a 24-bit recording-and-playback shield that handled the input and output, but not the microSD stuff, which is where I'm having trouble in the first place...nobody seems to be doing precisely this. But I'm sure that's partly because the Arduinos were slow and 8-bit; with the newer Arduinos and the Teensy, it seems like this ought to be possible.
I can provide the code if people are interested in trying my half-broken version as-is.
Thanks in advance!
-Nick
So, I'm trying to build an audio recording and playback device using Teensy 3.0. I'm pretty new at this stuff--I've been coding for years, but the hardware stuff still kind of feels like voodoo to me, so please bear with me if I make bizarre choices or silly mistakes. Arduino doesn't really seem fast enough for proper audio, but with Teensy's 32-bit processor, higher clock speed, and high-quality ADC, it seemed like audio might be within reach. This is for my sister, who does some fancy stuff with sampling and tracking for her music performance, but would like something she can use live at a venue. The input would be a microphone, probably either XLR or 1/4" jack; the output would be 1/4" jack line out to a PA system. Since it's just one mic input, monoaural is fine, but she'd like the audio to be fairly high quality.
Now, the Teensy ADC, being a successive approximation sampler, may not quite be suited to this, so I'm prepared to add an external ADC if necessary. I picked up one with a breakout board from sparkfun so I'll have it available if I need it.
Unfortunately, I haven't gotten as far as input yet...I'm having trouble already on output. I was hoping to achieve 44.1kHz 16-bit mono output. But samples for that take up quite a lot of space--the Teensy's onboard RAM holds less than a fifth of a second at that rate. So I loaded the sample (a copy of "Green and Blue" by Miles Davis, off "Kind of Blue", downmixed to mono) as a WAV file on a microSD card. Since the target design involves being able to record and play back from up to four different sample "slots," I named the file "1_44kHz.wav"--1 indicating the slot number, and 44kHz indicating the playback rate. I copied over a couple of reduced-sample-rate copies (22 & 11kHz, respectively) for additional testing.
Because I still need to do so much experimentation to get things working these days, I used the PCM56P audio chip from TI as my DAC; it's a 16-bit Burr-Brown DAC, capable of running at tremendous sample rates, with a convenient serial interface and an even more convenient 16-pin DIP package, so I can use it on a breadboard. The timing turned out to be fairly forgiving, and it's able to keep up as fast as I can bitbang the interface with digitalWriteFast(). (I was a little worried about this, because it says the max clock is 10MHz, but apparently I'm wasting enough clock cycles, even at a 96MHz clock, that the DAC has no trouble with it, and the 'volatile asm("nop\n nop\n")' sort of stuff I had in macros was unnecessary.) It required +5 and -5 voltage lines for input, which was a bit of a pain, but I had an ancient ATX 1.0 computer power supply sitting on a shelf, and ran some lines from that, which worked fine, though it's a bit bulky. I'll work out a more svelte power supply once I've gotten the kinks out of the workings.
For the sketch, I used the programmable interrupt timer (which required a bit of fiddling--not sure if they've sorted that in a newer beta of the code yet; I'm still using the system I described in an earlier post, "Teensy 3.0 and interrupts") to generate interrupts at 44.1kHz, and played back from a sample buffer. When the buffer runs out, I load a new set of samples from the microSD.
The trouble with this is that the microSD takes a while to read when it's a new access. The byte transfer rate is no problem, but the read latency is. I haven't figured out exactly how long the read takes; it's less than a millisecond (I used "millis()" to check for slow reads and output a message to Serial), but it's audible even at 11kHz, which means it's more than about 180 microseconds. Interestingly, the effect is quite subtle until the trumpet kicks in; it's loud and high-frequency, so that makes some sense, but it's striking how much more noticeable it is than with just the bass and piano.
I tried adding a "deglitcher," or sample-and-hold amplifier, but that didn't do much...which makes sense, since the problem is that it *stops* varying, not that it's varying when it shouldn't. A deglitcher is really only called for with this DAC when you're using it for stereo output--then you can use two deglitchers and an inverter to multiplex the output. But it seemed worth a try, anyway.
So I'd love advice and input on the project. I'm not exactly stalled--there are things I can think of to try with the project. The first is looking more at the documentation for the sdfat library; it's possible there are better ways to do sequential reads, or perhaps I can do something to align the reads with the block boundaries. I'm not overly hopeful on that front, though, since I'm currently using the standard read(buffer, num_bytes) format, and the buffer is quite large by Teensy standards, so the seek costs should be somewhat amortized there. I've also tried using a much smaller buffer (512B), to match the block size on the microsd, with much more frequent reads, but that just increases the *number* of latency waits, which is no good. It just makes the whole sample sound slowed-down and wobbly. Another possibility is setting up some SRAM chips to handle the buffering; that adds a fair amount of complexity, and the chips aren't necessarily huge anyway, but it'd definitely speed up memory access, and if the glitches were rare enough, they'd probably be fine. But this seems like the sort of thing other people have probably done before, and done better--is there a completely different approach that would work much better? Perhaps it'd be better to have an external SRAM chip, but also an external counter and latch and timer, so that the SRAM could feed directly in to the DAC, and the Teensy could just sneak in between those reads and burst-transfer data from the microSD to the SRAM. Or something along those lines. Anyone have thoughts on this, or on good approaches to audio in general?
I know there have been a lot of audio shields for Arduino in the past, but it seems like they're all either strictly mp3-playback-from-microsd-with-minimal-arduino-interaction, which doesn't really seem suited (not least because I don't want to try to implement mp3 audio compression on the teensy to store recorded stuff) or else 12-bit recording-and-playback shields. Or, in one case, a 24-bit recording-and-playback shield that handled the input and output, but not the microSD stuff, which is where I'm having trouble in the first place...nobody seems to be doing precisely this. But I'm sure that's partly because the Arduinos were slow and 8-bit; with the newer Arduinos and the Teensy, it seems like this ought to be possible.
I can provide the code if people are interested in trying my half-broken version as-is.
Thanks in advance!
-Nick