#57142 - agentq - Thu Oct 13, 2005 11:12 pm
Hi,
Does anyone know anything about the hardware ADPCM encoding on the DS? Apart from saying how to set it as a format, both DSTek and NDSlib have very sparse information.
I need to know how to set the initial sample and the starting table index for the decode.
#57148 - gladius - Thu Oct 13, 2005 11:25 pm
As dstek points out, the format for the ADPCM samples is as follows:
[7bit table index initial value][16bit initial value][4bit data][4bit data][4bit data][etc..]
When using ADPCM and loops, set the loopstart position to the data part, rather than the header.
#57149 - headspin - Thu Oct 13, 2005 11:27 pm
Has anyone even tried just converting a sample using sox to IMA-ADPCM and then setting the appropriate bit in REG_SOUND*CNT? Might work.
_________________
Warhawk DS | Manic Miner: The Lost Levels | The Detective Game
#57153 - agentq - Thu Oct 13, 2005 11:33 pm
Sorry gladius, you're right. It's right there in DStek, I must have missed it.
#57187 - headspin - Fri Oct 14, 2005 4:11 am
Please let me know when you get it working. I'm pretty sure quite a few people are interested in this also.
_________________
Warhawk DS | Manic Miner: The Lost Levels | The Detective Game
#57204 - agentq - Fri Oct 14, 2005 9:36 am
I'm not sure how many people have used it yet, so I'll post here with what I find.
#57291 - dnt - Fri Oct 14, 2005 8:57 pm
Using Sox to convert to IMA-ADPCM does not appear to work well. I was only able to get Sox to output this format into a wav file. This is not much good, since wav files split up the ADPCM data into separate chunks (to allow playback to start from not just the start of the sample). Each chunk has its own index and initial value. The DSTek docs suggest that a single chunk is required.
However, I found that the wav28ad.exe that is part of the 8ad ADPCM package by Damian Yerrick produces nearly the correct output (the index and initial sample values need to be swapped around).
Sadly, after all that I got no sound output. Perhaps the loop start always needs to be set to the data part (even when playing the sound in one-shot mode)?
Anyone else have any experience of actually making ADPCM work? I just gave up in the end and used a software ADPCM decoder. Bit of a shame given that the DS should do this itself...
#57325 - tepples - Fri Oct 14, 2005 11:01 pm
dnt wrote: |
However, I found that the wav28ad.exe that is part of the 8ad ADPCM package by Damian Yerrick produces nearly the correct output (the index and initial sample values need to be swapped around). |
I seem to remember that my code always sets initial sample to 0 and index to a constant. Either way, the source code also needs to be edited a bit to use the IMA stepsize tables rather than my customized tables.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.
#57780 - agentq - Tue Oct 18, 2005 2:04 pm
I've got it to work, playing audio from an ADPCM wav file encoded with the default windows encoder. It works fine.
although it has the click noise that seems to happen each time I restart an audio buffer, whether it's ADPCM or not. It seems that restarting the buffer doesn't have any effect for about 20 samples, which makes it click very annoyingly. I've sampled the output of the DS on my PC to confirm this.
I'm using this code to restart the sound.
Code: |
SCHANNEL_CR(0) &= ~SCHANNEL_ENABLE;
SCHANNEL_CR(0) |= SCHANNEL_ENABLE;
|
Anyone else find an answer to this? It's driving me mad.
Here's the sampled waveform I get:
[Images not permitted - Click here to view it]
I think MoonShell must have solved this, as his streaming audio doesn't click.
BTW, I also had to swap the initial sample and the table index around for it to work properly.
#57781 - 0xtob - Tue Oct 18, 2005 3:10 pm
agentq:
That's cool! I would love to know how you did it because I'm currently trying to play an ADPCM wav file too, but it does not work correctly.
I also had this clicking issue when restarting a sound buffer and to me it seemed that your problem is not the gap, but rather the step that comes after it. This step comes from abrupty stopping playback of the first sample. It's not a problem of the DS or of your method of disabling and re-enabling the channel, but a general problem with abprut stopping of digital audio.
My workaround for this was to fade out the first sample by decreasing the channel volume bit by bit starting 100ms before the next sample starts.
Of course, depending on what you want to do, other solutions are more appropriate.
Tob
#57784 - agentq - Tue Oct 18, 2005 3:37 pm
While there does seem to be a bit of a step between the two samples, that's a different problem and not what I'm trying to fix.
I'm trying to play a long sound in a looping buffer, and each time I switch buffers there's a gap as shown. I need to play the two buffers seamlessly to be able to stream the audio off the CF card. But the hardware seems to always make a gap before starting the new sound.
I can't fade in this case because these are two parts of the same waveform.
#57785 - 0xtob - Tue Oct 18, 2005 3:44 pm
Another approach would be to use one buffer, set the channel to loop and then always write to the half of the buffer that's not being played. It's like double buffering, only for audio and with only one physical buffer.
#57787 - agentq - Tue Oct 18, 2005 3:50 pm
That was another thing I tried - but there's no way of telling when the buffer has looped, as it can't generate an interrupt.
I tried setting a timer up to last exactly the length of the buffer so I could tell when it finished. That works for a while, but after about 10 seconds the two timings drift apart.
#57790 - 0xtob - Tue Oct 18, 2005 4:01 pm
That's strange. Are you sure the timer that called the sound rendering/copying function had the correct frequency? You could set it it to a divisor of the sampling frequency, like sampling_freq/64 (for example by using the DIV64 (or however it was called) flag for the TIMER_CR and count up until you reach the number of samples that are played per rotation and then call the rendering function.
I'm wondering because I once tried this approach in a PC program and it worked fine. You could also experiment with different buffer lengths.
#57793 - agentq - Tue Oct 18, 2005 4:19 pm
Yep, the timers are at the right speed.
The gap seems to be something to do with the hardware taking some time to start the sound, but that would be silly since a looping buffer is a very common thing to want to do.
Under Windows, timers will never stay in sync, but under DOS they did. I remember writing a sound driver for the Soundblaster that used timers.
#57802 - 0xtob - Tue Oct 18, 2005 5:28 pm
So the restarting of the channel is the problem somehow. Have you tried using channels 0 and 1 alternatively for each sound chunk? This would solve it if the channels need the short pause to recover.
If the gap is still there, then the dirty-hack-style solution would be to find out how long the buffer needs to start playing and enable it that much earlier. With that you probably won't reach 100% accuracy, but it may be good enough to be unnoticable.
#57806 - DekuTree64 - Tue Oct 18, 2005 5:39 pm
Sound timers and normal hardware timers run off the same clock, so they will work for sample counting as long as the periods are integer multiples of eachother. How are you calculating the periods for them?
Also, the clicking problem is probably because of the FIFO depth. I'm not sure the exact depth on the DS, but the GBA was 16 bytes. The click happens when you stop/restart the DMA in the middle of a batch of samples.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku
#57812 - agentq - Tue Oct 18, 2005 6:19 pm
Does this mean the solution is just to play a multiple of 16 samples?
EDIT: No, it can't be, I tried that :-(
#57819 - DekuTree64 - Tue Oct 18, 2005 7:03 pm
You're playing ADPCM though, right? So then it would need to be a multiple of 32 samples (2 samples in each byte). The FIFO may be deeper too, so try making the buffer size a full power of 2 as a test case to be sure that's not the problem.
Sample counting with a timer really should work more nicely though, so try setting the timers something like this:
Code: |
soundTimer = (1 << 24) / sampleRate;
REG_SCHN_TIMER(0) = 65536 - soundTimer;
REG_SCHNCNT(0) |= SCHN_ENABLE;
REG_TM0D = 65536 - soundTimer*2; // Ticks twice as fast, so period*2
REG_TM0CNT = TIMER_ENABLE | TIMER_IRQ | TIMER_DIV256; |
That should interrupt every 256 samples. You could also just multiply soundTimer by 256 instead of using TIMER_DIV256, but you'd probably overflow 16 bits.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku
#57861 - agentq - Tue Oct 18, 2005 10:44 pm
Well, it seems like I'll have to rethink how I'm doing my ADPCM.
Although thanks to DekuTree I have fixed my PCM buffer problems. The problem was the following code:
Code: |
SCHANNEL_TIMER(channel) = SOUND_FREQ(sampleRate);
...
TIMER0_CR = 0;
TIMER0_DATA = TIMER_FREQ(sampleRate);
TIMER0_CR = TIMER_ENABLE | TIMER_DIV_1;
TIMER1_CR = 0;
TIMER1_DATA = 0x10000 - ((bytes) >> 3); // Trigger four times during the length of the buffer
TIMER1_CR = TIMER_ENABLE | TIMER_IRQ_REQ | TIMER_CASCADE;
|
Due to the fact that 22050 doesn't go into 0x1000000 and 0x2000000, the two values SOUND_FREQ() and TIMER_FREQ() produce two different timings, makeing the buffers gradually drift away from each other! To fix it, I changed TIMER_FREQ(sampleRate) to SOUND_FREQ(sampleRate) * 2.
The ADPCM problem, which was the one I was originally posting about, is caused by the DS seemingly not being able to make a seamless transition between two different samples.
The WAV file I'm playing is build in 512 byte packets, each one containing a starting value, a table index, and 1016 samples. Because the DS only reads those values when it starts playing a sound, I need to start the channel again at a different address for each packet, and this seems to be impossible to do in a glitch-free way.
I think I'll just decompress the sample in software and play it as a plain old PCM. :-(
#57909 - gladius - Wed Oct 19, 2005 5:49 am
Instead of stopping one channel to play the next adpcm chunk, play the ext chunk on another channel. That's what I do and it works great for streaming sound. It does have the downside of requiring two channels and still needs the timer.
#57926 - agentq - Wed Oct 19, 2005 10:14 am
I did give that a go, but because the interrupt is sometimes delayed (because another interrupt may be being handled at the time) the new channel doesn't start at exactly the right time.
#57964 - gladius - Wed Oct 19, 2005 5:25 pm
Okay, you then need to prioritize the timer interrupt as the first one you handle. Also, you'll need to enable multiple interrupts (so if you are servicing one interrupt, another one can fire and not be blocked). Pretty much as long as you check IF & IRQ_TIMER first, and don't disable IE during the handler you should be okay (plus you need enough irq stack space).