Delta-Sigma audio delay in Arduino?

Started by earthtonesaudio, December 05, 2011, 09:16:04 AM

Previous topic - Next topic

earthtonesaudio

I was thinking it would be cool to construct a short-to-moderate audio delay, sort of similar to a PT2399, but with an Arduino to get some extra features not available with the PT2399.

So a feature set would be something like:

-1-bit delta sigma ADC (perhaps a dedicated, separate IC codec for this part)
-approximately 44kbits (not bytes!) shift register for the audio delay, with the clock-through rate externally variable (via pot or tap-tempo depending on how much memory is available)
-for chorus/flanger/doubling effects you could split this register into smaller chunks and clock data through at an LFO-modulated rate
-PWM output, external filtering back to analog.


...But, I don't have a feel for how much of this could be done inside an Arduino (let's say for example using an ATmega328, with ~30kB available space)... for instance, would this require external RAM to hold everything?

nexekho

Quote from: earthtonesaudio on December 05, 2011, 09:16:04 AM
I was thinking it would be cool to construct a short-to-moderate audio delay, sort of similar to a PT2399, but with an Arduino to get some extra features not available with the PT2399.

So a feature set would be something like:

-1-bit delta sigma ADC (perhaps a dedicated, separate IC codec for this part)
-approximately 44kbits (not bytes!) shift register for the audio delay, with the clock-through rate externally variable (via pot or tap-tempo depending on how much memory is available)
-for chorus/flanger/doubling effects you could split this register into smaller chunks and clock data through at an LFO-modulated rate
-PWM output, external filtering back to analog.


...But, I don't have a feel for how much of this could be done inside an Arduino (let's say for example using an ATmega328, with ~30kB available space)... for instance, would this require external RAM to hold everything?

On the 328, that 30kb is program memory, not RAM; even if you could read/write it quickly enough, the write cycles would run out in minutes.
I made the transistor angry.

earthtonesaudio

Good point, I was not sure about that.  In that case it would need to be an external RAM.

SPI serial RAM seems like the way to go for this application... cheap, fast enough, and big enough.  I can buy one of these for $0.86 from Digikey.

What I don't understand is what the latency would be for simply using the RAM as a large FIFO.  If possible I would like to get audio delays as short as 50ns for through-zero flanging effects, so I suspect the microcontroller latency will be the limiting factor.

DavenPaget

64kb ram ... Get more , i don't think it's enough ...
Hiatus

earthtonesaudio

#4
The PT2399 contains 44kbits, which is 5.5kB, which means you could fit eleven PT2399's worth of delay into 64kB.  The PT2399 can do 300ms fairly clean, so with 64kB of RAM that would be equivalent to 3.3 seconds of clean delay.

DavenPaget

Quote from: earthtonesaudio on December 05, 2011, 01:12:36 PM
The PT2399 contains 44kbits, which is 5.5kB, which means you could fit eleven PT2399's worth of delay into 64kB.  The PT2399 can do 300ms fairly clean, so with 64kB of RAM that would be equivalent to 3.3 seconds of clean delay.
Close enough .
Hiatus

g_u_e_s_t

ive had a project like that on my back burner for way too long now
the fastest you can get data in and out of an arduino
is using the SPI interface at 8Mbits/second
and thats assuming no interrupt handling latency
so thats 125ns min audio latency
although since things have to be handled bytewise
maybe its really 1us latency

i decided to use standard parallel dram
since its pretty innexpensive and fast and has a lot of space
and the arduino wouldnt handle any of the data
it only controlled the indexing of the dram

ill have another look at the serial sram chips
but i seem to recall having to clock in a byte that represented the desire to write
followed by the address you wanted to write to
and then the data at that segment
and if you wanted to read data back out
you had to go through the same process
so it was going to take forever

earthtonesaudio

Thanks guest, having some actual numbers to look at is very helpful.

The SRAM I linked to (23x640) was chosen arbitararily; I just searched Digikey for SPI RAM and sorted for cheap and in-stock.  But looking closer at the datasheet, it has a "sequential mode" which automatically increments its internal address register, so you only have to tell it to write once, then you can fill the array (and overwrite) in order by simply clocking in data.  Reading can be done sequentially as well.

Being a hardware guy, this immediately makes me think to use two identical SRAMs; one writing while the other is reading and vice versa.  So the Arduino would say to one chip "read sequentially" and to the other it would say "write sequentially" then after a little less than 64000 clock cycles it would give the opposite command.  That would require 3 pins from the Arduino (two data outputs and one clock output) and you could simply OR the SRAM outputs together.
With two SRAM chips, I *think* the operations could be overlapped such that there was no break in the data output stream, but I'm not completely sure.


I'm not completely against the parallel DRAM idea but the chips are much larger and seem to require more AVR pins to operate them.

g_u_e_s_t

i like the idea of dual serial sram chips
those chips tend to be pretty cheap and small

im still a bit concerned about using the arduino
to sample the data coming from the sigmadelta

there is a 1.5 clock cycle setup time on the inputs to avr chips
so you need to wait around 2 clock cycles before sampling inputs
just to be sure the state is what it says it is
so this will incure a bit more delay

im also not sure how the variable clock rate will be implemented
will the arduino be clocked externally as well?

earthtonesaudio

Here is what I was picturing:

-Arduino gets its clock signal normally, from a 16MHz crystal or what have you.

-Internally it generates an "effect clock" which is variable according to how it is programmed.  The speed of this clock would necessarily be slower than the Arduino's crystal.  I'm not sure what a reasonable speed is for an Arduino-generated signal.  Maybe 1MHz?  Please correct me if this assumption is off.

-The Delta-Sigma ADC runs off the "fast" 16MHz crystal clock source, but the Arduino has setup and hold requirements.  No problem!  If you think about it this means that the Arduino acts as the decimation filter for the ADC, thereby improving its signal-noise ratio.  In fact it might even be preferable to sample the inputs less frequently than the Arduino's maximum rate.

This is about as much as I have thought it through so far.  I don't yet have an Arduino but I'm expecting to get a Nano from Santa this year.  :)

cpm

Consider that the PT2399 chip, as to the datasheet, is runnign at 2Mhz for 300ms, but can go up to 20M for shorter times...

Also, i dont know exactly how many Clock cycles takes up to execute one instruction in the AVR (for PICs it takes 4). Seems like its a 1:1, so optimistically it'll be 16Mips. A 1-bit sampling at 2Mhz means you've got only 8 instruction cycles to get everything done before the next sampling takes place... Thats a very tight margin

earthtonesaudio

That's an interesting point about the number of instruction cycles, Carlos.

From the PT2399 datasheet's tables of clock frequency and delay times (together with 44kbits of memory):

The equation [fck(clock cycles/second) * td(delay time in seconds) / 44kbits]  will give us the number of clock cycles that go by while a bit of data is stored in a particular location.  From the table I get a number around 15.5 so it seems reasonable to conclude that the PT2399 takes about 16 clock cycles to move an *average* bit from its current address to its next address.


With the two-SRAM setup I am imagining, the time-critical instructions would be relatively few and far between.  For instance each time you alternate SRAM chips, you need to send that chip an 8-bit instruction and a 16-bit address to tell it where to start reading/writing from.
Maybe it can be simplified enough to make the margin more relaxed somehow...

g_u_e_s_t

the arduino is essentially 1 clock per instruction
a few things take 2
like multiplies and data fetches from internal memory

if the adc is getting clocked at 16mhz
but is only being sampled at 1mhz
is the data getting buffered between them?
or do you just lose those other 15 bits?

is it the variation of the internal clock runing at 1mhz
that will cause the flanger effects and whatnot?
or will variable delay data fetches get used?

if its variations in the internal clock
that will be a bit choppy
as you can only divide by integer factors

also be aware that there is a lot of overhead in the arduino language
so things dont move as fast you think they should

earthtonesaudio

Hi again, had to do some serious reading and come back to this topic.

I originally thought that simply sampling the ADC output at a lower rate is equivalent to decimation.  Now I think they are not quite the same thing.  Still, sampling at 1MHz is 50x oversampling for 20kHz audio.  Guitar generally has very little info above 10kHz so I think 1MHz sampling is sufficient.  Whether the ADC is clocked at 1MHz or just sampled at 1MHz seems to not matter very much in my mind.



It seems to me there are two main ways to get a variable delay time using two RAMs.  One way would be to vary the clock frequency.  Another would be to use a fixed clock frequency but use a variable amount of the available RAM.  For example write 1000 bits and then switch to the other RAM.  It seems like the second method would be less resource-intensive.

cpm

Quote from: earthtonesaudio on December 12, 2011, 03:28:22 PM
It seems to me there are two main ways to get a variable delay time using two RAMs.  One way would be to vary the clock frequency.  Another would be to use a fixed clock frequency but use a variable amount of the available RAM.  For example write 1000 bits and then switch to the other RAM.  It seems like the second method would be less resource-intensive.

or some kind of ring buffer... one index writes, and other reads, how far they are apart is the delay time (at a constant clock)... very similar to magnetic tape heads.

earthtonesaudio

Exactly.  Then to implement a variable delay time you would need a variable-length timer.

g_u_e_s_t

varying the clock speed has 2 advantages
first its easier to do
you just vary the clock
and second it smoothly transitions between delay times

but it also has two downsides
it can be tricky to vary a microcontroller clock
without glitching the microcontroller
and it also reduces the effective oversampling rate
as you go to longer delay times

i was having a lot of trouble
getting smooth delay time variations
with the variable buffer size implementation
i got all this digital noise when i skipped samples
to move to the new buffer point

i have a chart i put together a while back
comparing oversmampling rate with bit depth
for doing this sort of 1bit sampling
if i can dig it up ill post it

free electron

Quote from: earthtonesaudio on December 12, 2011, 03:28:22 PM
Whether the ADC is clocked at 1MHz or just sampled at 1MHz seems to not matter very much in my mind.
There are two important facts if you a planning to use atmega328 for experiments:
1. The ADC converters are not 1bit Sigma-Delta, but 10bit successive approximation.
2. The max clock you can use for ADC at 10bit resolution is about 200kHz. It can go higher if you reduce the bit resolution.

Let's say the ADC is clocked at 200kHz, it doesn't mean that the sampling frequency will be the same value. The adc conversion process is split in two parts: sample&hold and conversion. Both use a multiple ADC clock cycles to complete. Take a look at the datasheet:
http://www.atmel.com/dyn/resources/prod_documents/doc8161.pdf
page 253
to see how the ADC module on Atmega operates.
Usually you'd need to set up a timer to trigger an interrupt at the required conversion frequency (f.e. 32kHz) and do all the process (sampling, storing in RAM, reading from RAM, sending to DAC) in the interrupt subroutine.

QuoteIt seems to me there are two main ways to get a variable delay time using two RAMs.  One way would be to vary the clock frequency.  Another would be to use a fixed clock frequency but use a variable amount of the available RAM.  For example write 1000 bits and then switch to the other RAM.  It seems like the second method would be less resource-intensive.
The usual approach is the second method + the use of interpolation techniques to calculate the values that are between the samples to get a smooth delay time change.

Here's another nice board to play with DSP:
http://www.st.com/internet/evalboard/product/252419.jsp
I've bought it recently for about 16EUR. Not bad for an 32bit MCU with 1MB flash, 192kB RAM (!), intergrated DAC ond lots of other interesting stuff.


earthtonesaudio

At the very beginning of this thread I was considering using the Arduino's ADC but quickly realized it was too limited and simply not the right tool for the job.

At this point, I think the Arduino would be just incrementing a (user-controllable) counter and sending the switch instructions to the two RAM chips at the appropriate intervals.  The audio signal would be presented to the Arduino in already-digitized format.  Technically the Arduino would not even have to touch the audio data except that seems like an easier way to multiplex the RAM inputs.


That ST chip looks awesome, but for me it would realistically cost $200+ due to my lack of Windows.  For my budget, anything over $50 is a non-starter.

g_u_e_s_t

if you were considering doing the traditional ADC to DAC route
the leaflabs maple or maple native with 1MB of RAM is not too expensive
and can be programmed with the arduino environment
http://leaflabs.com/store/
or theres the codecshield i designed
http://www.openmusiclabs.com/projects/codec-shield/
but that doesnt get you much delay time

ultimately i think the sigmadelta delay has a far more interesting sound anyways
it starts going crazy when it gets clocked too slow

since you will have to process things pretty quickly inside the arduino
i would reccomend the following for your main loop


void loop() {
  while(1) {
    // your code here
  }
}


there are a bunch of jumps inside the main loop that arduino does
and these take up a fair bit of time
putting the while(1) bypasses all that stuff