Shop OBEX P1 Docs P2 Docs Learn Events
Preparing 16bits for 24bit playback methods — Parallax Forums

Preparing 16bits for 24bit playback methods

T ChapT Chap Posts: 4,223
edited 2014-01-30 08:57 in Propeller 1
I have hacked together various objects to create an I2S SD wav player engine. It is an SD card file reading method to get stereo 16bit wav files into a dual buffer, then drive a DAC engine in PASM that transfers the 16 bits out in a 24bit I2S format(MSB first) to the DAC that wants signed data. The test is a 5 second simple sine wave that is 16 bits stereo in a wav file. The buffer is transferring the entire file, but what I hear is cyclical white noise with a hint of tone to it.

I adapted a PASM method of transferring the 24 bits with the MSB output shown below:
:bit23      rcl       LeftData, #1        WC
              muxc      OutPutMask, DataOutPinMask      ' SETUP Data bit MSB 
              WAITPEQ   HIGH, bclkMSK         
              WAITPEQ   LOW, bclkMSK     ' Must Wait for Bitclock LOW to change output
              mov       OUTA, OutPutMask            ' DRIVE Data

I think the distortion of data may be a result of the method I have tried to use that does the Rotate and Carry Left.

RCL info: If the WC effect is specified, at the end of the operation, the C flag is set equal to Value’s original bit 31.

But, I am not working with 32 bits, I am working with 16 bits shifted left to 24 to allow the sign bit of 16 bits to sit at bit 24 so that the output is 24bit signed.

LeftData long 0
RightData long 0

Since LeftData and RightData are longs( originally filled with a 16bit word, then bitshifted left), I am thinking the RCL is not doing what it did in the original version that Mark T posted.

Bottom line is, can anyone suggest a method to take signed 16 bits, move it to 24 bit format maintaining the sign, then allow the each of the 24 bits to be prepared for output one at a time based on my bitclock as the timing?

I do not want to convert the individual outputs to a loop concept, as the LSB requires a LR clock to toggle before it is sent, and I found that injecting code to insert it in a loop was affecting the timing, so it is best to keep the 24 x 2 = 48 sections as it is easier and less headaches since the timing is already pushed to far on the Prop.

Comments

  • RaymanRayman Posts: 14,659
    edited 2014-01-28 09:23
    If you're getting 16-bits with a rdword, then you'd need to shift 16-bits to the left to get the first bit into position to shift left into the carry...

    So I think all you need to do is change the #8 to #16 in this section:
    :startleft    ' GET THE L E F T WORD
                  'rdlong    LeftData, LeftDataaddr  ' REQD read LEFT data    tones 6
                  MOV       LeftDataaddr, PlayBuff
                  rdword    LeftData, LeftDataaddr        ' read LEFT data
                  shl       LeftData, #8
    
  • T ChapT Chap Posts: 4,223
    edited 2014-01-28 09:32
    Ray, I will check again but I am pretty sure I have tried shl 16 and got similar results.
  • T ChapT Chap Posts: 4,223
    edited 2014-01-28 12:10
    Ray, yet that is an improvement changing to <<16, at least now I can tell it is the actual file so I can address the other timing issues. One channel is still very distorted even though they are both playing the right length with no drop outs. Somewhere there is still some issues getting the file out correctly. Thanks.
  • AribaAriba Posts: 2,690
    edited 2014-01-28 14:24
    Why do you first set the bit in the OutPutMask register and write this later to the OUTA latch?
    :bit0         rcl       LeftData, #1        WC 
                  muxc      OutPutMask, DataOutPinMask
                  WAITPEQ   HIGH, bclkMSK
                  WAITPEQ   LOW, bclkMSK 
                  mov       OUTA, OutPutMask
    
    I think the output data must be present when you wait for the BCLK going high. With your code the bits are all fetched one position too late by the DAC, and the first (the sign bit) comes not from the right channel.
    I would try to change the code for the single bits to:
    :bit0         rcl       LeftData, #1        WC 
                  muxc      OUTA, DataOutPinMask
                  WAITPEQ   HIGH, bclkMSK
                  WAITPEQ   LOW, bclkMSK 
    
    Because this spares one instruction you can now output the bits in a loop. Here is a draft of an alternative output loop, that also minimizes hub access. It does not handle the buffers.
    or  outa,lrmsk
          mov smpcnt,nSamples
          waitpne bclmsk,bclmsk   'wait until bitclock low
          waitpeq bclmsk,bclmsk   'wait until bitclock high
    
    loop  rdlong smp,DataAddr     'read 16bit left + 16bit right into one long
          add DataAddr,#4
    
          mov ctr,#16
    Left  shl smp,#1  wc          'output 16 left bits
          waitpne bclmsk,bclmsk
          muxc outa,datmsk
          waitpeq bclmsk,bclmsk
          djnz ctr,#Left
          mov ctr,#7              'expand to 24 bits (repeat LSB16 8 times)
    ExpL  waitpne bclmsk,bclmsk
          waitpeq bclmsk,bclmsk
          djnz ctr,#ExpL
          waitpne bclmsk,bclmsk   '8th expand bit
          andn outa,lrmsk         'change LR-clock for LSB24
          waitpeq bclmsk,bclmsk
    
          mov ctr,#16
    Right shl smp,#1  wc          'output 16 right bits
          waitpne bclmsk,bclmsk
          muxc outa,datmsk
          waitpeq bclmsk,bclmsk
          djnz ctr,#Right
          mov ctr,#7              'expand to 24 bits
    ExpR  waitpne bclmsk,bclmsk
          waitpeq bclmsk,bclmsk
          djnz ctr,#ExpR
          waitpne bclmsk,bclmsk   '8th expand bit
          or  outa,lrmsk          'change LR-clock for LSB24
          waitpeq bclmsk,bclmsk
    
          djnz smpcnt,#loop
    done  ,,,
    

    Andy
  • T ChapT Chap Posts: 4,223
    edited 2014-01-28 15:05
    Thanks Ariba! I will test these ideas out. Great points.

    The idea to go direct with muxc to outa is still distorted. I will work on your new loop idea instead and other suggestions to see what happens.
  • Mark_TMark_T Posts: 1,981
    edited 2014-01-28 16:47
    Remember I2S sends the MSB one BCLK cycle after the LRCLK transitions, although many chips also support
    left-justified and right-justified modes.

    So one does: synchronize to BCLK falling edge and set LRCLK, synchronize to next BCLK falling edge and output MSB, etc etc.
    Inputs are sampled on the rising edge of BCLK.
  • T ChapT Chap Posts: 4,223
    edited 2014-01-28 16:54
    Mark, I think that I have it so that the LR clock changes state justbefore the LSB is sent, which is the same as having the MSB sent after the first bitclock following the LR change. It does puzzle me as to how to send the first word on the left channel in this way though.

    On one channel, I am getting tons of distortion but can clearly hear the sample. The other side is much clearer but still clipping. I wonder if there is any gain being introduced by moving the data left the equivalent of 8 bits(16 bits moved to align to MSB of long)
  • T ChapT Chap Posts: 4,223
    edited 2014-01-28 17:32
    Mark, I started thinking in terms of flipping LR prior to the MSB versus flipping it prior to the LSB. Now I have both channels with the same distortion, much less all together. This was some good progress.
    'right channel transition 
    
                  ADD       PlayBuff, #2
    
                ' GET THE RIGHT WORD
                  MOV       RightDataaddr, PlayBuff
                  rdword    RightData, RightDataaddr        ' read LEFT data
                  shl       RightData, #16
                  WAITPEQ   LOW, bclkMSK     '1. synchronize to BCLK falling edge
                  andn      OUTA, LRmask     '2. set LRCLK
                  WAITPEQ   HIGH, bclkMSK    '3. synchronize to next BCLK falling edge
                  WAITPEQ   LOW, bclkMSK     '4. output MSB
    
    
    
  • AribaAriba Posts: 2,690
    edited 2014-01-28 18:47
    What frequency has the BitClock?
    Do you have a Scope to watch the signals?

    Andy
  • T ChapT Chap Posts: 4,223
    edited 2014-01-28 19:34
    Success! Finally after countless hours I have the board working playing very nice audio over the I2S ES9023 Sabre DAC. I have the old Protools 888/24 converters and also the new Apogee Symphony converters for Protools 10, so I am very critical of audio playback. For just getting this thing off the ground, I am very pleased with the sound. Thanks to you guys for making some suggestions. The re-focussing on the LR clock tonight got rid of the distortion. BTW there are a number of cheaps boards with this DAC on it, I highly recommend any Prop + Audio enthusiasts to check it out. I will post the complete set of files soon.

    Ariba, I don't have the bitclock set perfect yet, I have it is a cycle length of .5uS. It should be slightly less than that to be perfect. I have added a PLL1708 master clock that feeds the dac at 768*44100 and feeds the Prop at 384*44100. The Prop has a counter dividing it down and syncing the start of the bitclock to 48 clocks per sample(2 * 24 bits).

    The flags show the start of the bitclock at the MSB. There are 25 bits per LR clock, one clock is padding at the end of the data.
    984 x 262 - 125K
  • AribaAriba Posts: 2,690
    edited 2014-01-28 19:55
    Good to hear.

    I have a Propeller board with a Wolfson Audio DAC here (WM8762) but had not yet the time to play with it.

    Andy
  • oscar4oscar4 Posts: 9
    edited 2014-01-29 06:04
    New here & was attracted to Parallax's approach to handling of real-time event management
    Very interested in audio & my main first project will be to use this board as a test board to evaluate various DAC chips, some of which will be non-audio chips.
    So looking for an easy to program/configure solution which can deal with the various chip communication interfaces I2S, RJ, LJ (and all the variants of these), parallel, etc.

    T Chap - this looks like a good starting point & look forward to seeing your finished code
    BTW, you might be getting away with using a non-synchronous clock because it is the ES9023 chip you are using which can work with an asynch or synch clock. Sounds better with a synch clock, though.
    What are you using as a signal analyser, btw - I'm looking for a good cheap solution?
  • T ChapT Chap Posts: 4,223
    edited 2014-01-29 06:43
    Edit: Welcome to the forum Oscar. Lot's of fun to be had with the Propeller.

    I have the Saleae Logic analyzer that Parallax has on their site, it has built in options to examine various formats including I2S. I also have the PropScope for simple and portable uses. Now that the idea is flushed out for the I2S data flow, connecting other devices is easy. I have an external master clock that the Prop derives it's bitclock and word clock from, but on the 9023, I am not sure that the Prop couldn't generate all the clocks needed for simple audio playback needs depending on the xtal.

    These files are a mess, most of this is my first real effort to start learning PASM, so there is room for improvement. I included the ariba starting point but it needs a good deal of work still.

    Audio_SDPlayer launches the external master clock, then the bitclock engine, dac engine, and manages the SD reader FSRW.

    The code is derived from Raymans QMP player which uses a double buffer handled in SPIN using FSRW. The DAC engine was derived from Mark T's Wolfson I2S DAC code, although his original code was left justified and I have modified it to I2S which is slightly different.

    I did study Kye's FAT engine, V2 player etc, but it was too far over my head to dissect it and convert. Although, at this point with a working DAC output, it would be much easier to adapt to any other buffer method. For me, the hardest part is getting a clear mental picture of all of the elements that must work together: 1. Reading the SD into a dual buffer with empty flags(level meters as Jonny Mac says). 2. Putting the data into a format that can be sent to the DAC chip. 3. Determining the bitclock and LR clock timing. 4. The DAC pcb.

    For my own purposes I intend to create a simple graphic for future reference of the elements to make it work.
  • oscar4oscar4 Posts: 9
    edited 2014-01-29 07:07
    T Chap wrote: »
    I have the Saleae Logic analyzer that Parallax has on their site, it has built in options to examine various formats including I2S. I also have the PropScope for simple and portable uses.
    That's great info, thanks - I'll have a look at the Saleae. I don't need a portable oscope, so the Proscope is probably not of much interest.
    Now that the idea is flushed out for the I2S data flow, connecting other devices is easy.
    Sure & thanks to you & all who have contributed
    I have an external master clock that the Prop derives it's bitclock and word clock from, but on the 9023, I am not sure that the Prop couldn't generate all the clocks needed for simple audio playback needs depending on the xtal.
    Yep, I knew you had an external clock & this was being used by the Prop, through a PLL, as it's internal clock. But, from what I gathered, the PLL divider wasn't quiet correct so does this not generate I2S signals that are not synchronous with your master clock feeding the ES9023? I'm sure the Prop could do the necessary PLL divide to generate all the clocks needed for audio but for lower jitter (& best sound) an external clock directly reclocking the I2S signals before input to the DAC chip would be best.
    As I said, I'm looking into this prop board for evaluating various DAC chips so I want to ensure optimum operation to be able to achieve this (or as optimum as I know how).
  • T ChapT Chap Posts: 4,223
    edited 2014-01-29 07:25
    Oscar, the external clock PLL1708 is feeding the DAC at 768*44100. It feeds the Prop at 384*44100. What I have noticed, and could be completely wrong, is that with the 12.288xtal on the Prop, I can divide down and sync to the external clock no problem, UNTIL the bitclock period needs to get below .500uS. It seems that the code in PASM takes too much time to drop that low, at least that's my experience, someone more familiar with PASM may disagree. To sync to 384*44100, my path was to waitpeq ( wait for pin to equal "1"), then start counting pulses using a counter in POSEDGE mode, so that it counts the incoming master clock, accumulates to PHSA. Then, read in a loop PHSA value and it is equals or exceeds 4 clocks(4 = high phase of bitclock cycle), then turn on the bitclock high phase. After some time ( 50:50 duty), turn off the bitclock. Now, as some others have suggested in my other threaded about dividing a clock, it may be much easier to use a /8 chip instead, which means less stringent timing concerns on the Prop. This may be added to the next board, at least as a patchable/insertable option from the master clock to the Prop. For now, I am not that concerned about the larger bitclock, as at 44100 the LR clock should be around 22uS per cycle, and I am at 24uS. I would predict that some options come up that are better than what I am doing in terms of dividing the clock. For example, if instead of the LR clock being derived from the BItclock, what if the LR clock was the true timing ( much easier as it is 48 times slower than bitclock), and let the bitclock start it's cycle and reset each LR clock? I am understanding that you can send the bitclock at a higher rate if needed to the 9023, and excess data will be ignored beyond 24, but the true timing element is the LR clock. So, in other words, it may be that my divider should be using the counter to count to 48 versus 4 clocks, which is much easier, then have the bitclock reset at it's own async rate per LR clock. It is drifts a little between LR clocks, no big deal, just don't drift late, drift faster. As you will notice in the screen shot, I am sending more than 24 bitclocks per LR clock.
  • oscar4oscar4 Posts: 9
    edited 2014-01-29 11:18
    T Chap wrote: »
    Oscar, the external clock PLL1708 is feeding the DAC at 768*44100. It feeds the Prop at 384*44100. What I have noticed, and could be completely wrong, is that with the 12.288xtal on the Prop, I can divide down and sync to the external clock no problem, UNTIL the bitclock period needs to get below .500uS..
    Sorry, I'm not yet familiar with the prop - what is the PLL1708? Why do you need another xtal on the prop - can this prop clock not come from the external clock?
    It seems that the code in PASM takes too much time to drop that low, at least that's my experience, someone more familiar with PASM may disagree.
    Is this because you are trying to synchronise two different clock domains which is not advisable & will result in timing slippages?
    To sync to 384*44100, my path was to waitpeq ( wait for pin to equal "1"), then start counting pulses using a counter in POSEDGE mode, so that it counts the incoming master clock, accumulates to PHSA. Then, read in a loop PHSA value and it is equals or exceeds 4 clocks(4 = high phase of bitclock cycle), then turn on the bitclock high phase. After some time ( 50:50 duty), turn off the bitclock. Now, as some others have suggested in my other threaded about dividing a clock, it may be much easier to use a /8 chip instead, which means less stringent timing concerns on the Prop. This may be added to the next board, at least as a patchable/insertable option from the master clock to the Prop. For now, I am not that concerned about the larger bitclock, as at 44100 the LR clock should be around 22uS per cycle, and I am at 24uS. I would predict that some options come up that are better than what I am doing in terms of dividing the clock. For example, if instead of the LR clock being derived from the BItclock, what if the LR clock was the true timing ( much easier as it is 48 times slower than bitclock), and let the bitclock start it's cycle and reset each LR clock? I am understanding that you can send the bitclock at a higher rate if needed to the 9023, and excess data will be ignored beyond 24, but the true timing element is the LR clock. So, in other words, it may be that my divider should be using the counter to count to 48 versus 4 clocks, which is much easier, then have the bitclock reset at it's own async rate per LR clock. It is drifts a little between LR clocks, no big deal, just don't drift late, drift faster. As you will notice in the screen shot, I am sending more than 24 bitclocks per LR clock.
    I don't think you can just let the bitclock run free? If you synchronise your clock (by using one clock for all your timing) then I imagine you wont have this timing slippage.

    My concept for the eventual config for this design is two external audio clocks - one for each speed family. One of which is enabled depending on what the samplerate of the audio file being played. This can be determined when the start of the file is read & an enable signal sent out through one of 2 prop pins. The enabled clock signal being fed back to the prop & all clocking is then synchronous. Does this make sense & how feasible is it to implement?
  • T ChapT Chap Posts: 4,223
    edited 2014-01-29 12:14
    I used an external IC for the clock, it has it's own 27m xtal and it generates various clocks useful in audio data ie 44.1 and 48. The IC is configurable, so you would switch it to suit 44.1, 48 etc. The prop has it's own timing that is from it's 12.288 xtal. The prop derives it's bitclock from dividing down the external clock rate of 384 * 44.1 ( in a case of a 44.1 file). The DAC is also sent a higher master clock rate of 768 * 44100. So the master clock to dac, master clock/2 is sent to the prop( for dac sync,not prop's internal timing), the bit clock, and the lr clock are all derived from the PLL1708 master clock. That is, until you try to divide to a timing that the prop can't handle in PASM, then you hit a brick wall. I could be wrong, but I have found that a bitclock high phase shortest limit is .500us based on the code time required to count the positive edges of the master and divide it down. Maybe this will be solved, but coincidentally, the .500uS is only a slight amount higher the approx .472uS required for 1/44100(=0.00002267573696 sec or 22.67573696 uS) / 48 clocks per sample or 0.4724111866969 per bitclock cycle. With the PLL1708, you can create various clock outputs and easily toggle from sample rates on the fly between files, but the prop's internal clock system stays as is with it's own xtal.

    If you see this datasheet, notice that is shows quite a few more bitclocks than actual data bits, so it is ignoring anything past the LSB bit 0.

    To be honest, I am very new to figuring this out, these are just my perceptions and discoveries so far, which can easily be wrong.
    724 x 147 - 27K
  • oscar4oscar4 Posts: 9
    edited 2014-01-29 13:02
    OK, maybe someone with more experience than either of us can comment on this bitclock timing issue?
    And if my idea is feasible of using 2 independent external audio clocks (only one being enabled to suit the speed family of the audio signal)?
    I thought I read somewhere that the prop can be run solely from an external clock but this may have been with the Teensy board which I'm also looking into?
  • evanhevanh Posts: 15,928
    edited 2014-01-30 04:33
    T Chap wrote: »
    I used an external IC for the clock, it has it's own 27m xtal and it generates various clocks useful in audio data ie 44.1 and 48. The IC is configurable, so you would switch it to suit 44.1, 48 etc. The prop has it's own timing that is from it's 12.288 xtal.

    If you can possibly use a single crystal clock source for all parts then do so. Otherwise you have to pay extra close attention to data synchronisation.
  • oscar4oscar4 Posts: 9
    edited 2014-01-30 04:41
    evanh wrote: »
    If you can possibly use a single crystal clock source for all parts then do so. Otherwise you have to pay extra close attention to data synchronisation.
    I agree & would go so far as to say that you will never avoid timing drift using two clocks
    But, I'm new to this can the Prop use an external clock for it's timing?
  • Mark_TMark_T Posts: 1,981
    edited 2014-01-30 08:57
    oscar4 wrote: »
    I agree & would go so far as to say that you will never avoid timing drift using two clocks
    But, I'm new to this can the Prop use an external clock for it's timing?

    Yes, look up _CLKMODE in the Prop manual
Sign In or Register to comment.