Shop OBEX P1 Docs P2 Docs Learn Events
Mixing WAV Samples — Parallax Forums

Mixing WAV Samples

bradharperbradharper Posts: 64
edited 2010-09-23 18:00 in Propeller 1
I'm working on an effects engine where I need to loop one WAV file continuously, and other files, same audio specs, are triggered to play simultaneously. I've tried a few different mixing strategies, but each results in unacceptable levels of noise, or complete distortion.

Essentially, I have two event-driven Buffer objects fetching audio, via fsrw, from a microSD card, applying a volume adjustment, and then writing the samples to memory where the Mixer object examines each sample pointer, and renders the samples after dithering and mixing if necessary.

Both the Buffer objects and the Mixer are modified versions of the efx_wavdacs code from this thread:
http://forums.parallax.com/showthread.php?t=123900&highlight=audio+player

Here is the Mixer loop (spin file also attached) where I'm attempting to mix the samples.
DAT                      
                        org
' ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 
'                       I/O Setup
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
entry      
        or              dira,dira_              'set dira bits
        mov             ctra,ctra_              'set ctr's to output duty to pins
        'mov             ctrb,ctrb_  
        'mov             frqa,   quiet
        'mov             frqb,   quiet                      
'       

' ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 
'                       Main Sample Loop
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////                                   
:loop   
        rdbyte          mute_flag, mute_flag_pntr
        cmp             mute_flag, #1 wz
        if_z            mov     frqa,   quiet
        'if_z            mov     frqb,   quiet 
        if_z            jmp     #:loop

' /////////// Track 1 Reader                        
:tr1
        rdlong          tr1_lsample,tr1_pntr wz 'read samples
        'if_z           jmp #:loop
         'add           to_mix, #1  
        xor             tr1_lsample,h80008000           'convert signed samples to unsigned
        mov             tr1_rsample,tr1_lsample         'split and msb-justify them
        shl             tr1_lsample,#16
        and             tr1_rsample,hFFFF0000                        
        'add            lsample_sum, tr1_lsample
        'add            rsample_sum, tr1_rsample
' /////////// Track 2 Reader                        
:tr2
        rdlong          tr2_lsample,tr2_pntr wz
        'if_z           jmp #:out
        'add            to_mix, #1        
        xor             tr2_lsample,h80008000           'convert signed samples to unsigned
        mov             tr2_rsample,tr2_lsample         'split and msb-justify them                        
        shl             tr2_lsample,#16
        and             tr2_rsample,hFFFF0000 
        'add            lsample_sum, tr2_lsample
        'add            rsample_sum, tr2_rsample

' ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 
'                       Mix Samples
' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////                         
:mix    
        'add            tr1_lsample,tr2_lsample 'add samples
        'add            rsample_sum,dither
        'sar             tr1_lsample, #1                 ' signed divide by 2
        'sar             tr2_lsample, #1                 ' signed divide by 2
        adds            tr1_lsample, tr2_lsample        ' add together
        ''sar           tr1_lsample, #1                  ' signed divide by 2

        'test           lfsr,taps       wc      'iterate noise generator
        'rcl            lfsr,#1
         
        'mov            dither,lfsr             'make dither from noise
        'sar            dither,#ditherdown
         
        'add            tr1_lsample,dither          'add dither to samples

        'sar             tr1_lsample, #1

        'sar             tr1_rsample, #1
        'sar             tr2_rsample, #1
        'adds            tr1_rsample, tr2_rsample


:out
        mov             frqa,tr1_lsample        'output samples
        'mov            frqb,tr1_rsample
        jmp             #:loop                  'again...
 

quiet                   long    $80000000                                      

tr1_pntr                long    0-0
tr2_pntr                long    0-0

dira_                   long    0
ctra_                   long    0
ctrb_                   long    0

h80008000               long    $80008000
hFFFF0000               long    $FFFF0000
taps                    long    $80000062
lfsr                    long    1
zero                    long    0
lsample_sum             long    0
rsample_sum             long    0


mute_flag_pntr          long    0
mute_flag               byte    0
tr1_lsample             res     1
tr1_rsample             res     1     

tr2_lsample             res     1
tr2_rsample             res     1   

to_mix                  res     1
dither                  res     1 


Anyone have any suggestions about what I may be doing wrong here? I've tried the following:

1) Summing the samples.
2) Dividing each by two, then summing.
3) Summing then dividing by two.

All three create noise and/or distortion. Essentially, if I touch the sample at all (other than the dither) it's ruined. Audio is crystal clear so long as I'm only iterating over a single track buffer.

Thoughts?

-BH

Comments

  • AribaAriba Posts: 2,690
    edited 2010-09-04 19:59
    Please edit your post and make the <code> tags to [ code ]

    Why do you convert the samples to unsigned values? It's much easier if the samples are signed. Just separate the samples then divide first and then add.
    Here is a code fragment, how I would do it:
    loop    rdlong rsmpl,pointer1  'read L+R signed samples of first track
            mov    lsmpl,rsmpl     'left word in bit 31..16
            shl    rsmpl,#16       'right word to bit 31..16
            sar    lsmpl,volume1   'attenuate the samples (by 2^n)
            sar    rsmpl,volume1   'volume should be min. 2 to make some headroom
            mov    suml,lsmpl
            mov    sumr,rsmpl
    
            rdlong rsmpl,pointer2  'read L+R signed samples of second track
            mov    lsmpl,rsmpl
            shl    rsmpl,#16
            sar    lsmpl,volume2
            sar    rsmpl,volume2
    
            add    suml,lsmpl      'mix the samples
            add    sumr,rsmpl
    
            mins   suml,minval     'optional limiter
            mins   sumr,minval
            maxs   suml,maxval
            maxs   sumr,maxval
            shl    suml,#1
            shl    sumr,#1
    
     
            add    suml,offset     'write to DACs
            mov    frqb,suml
            add    sumr,offset
            mov    frqb,sumr
    
            ...                    'update sample pointers and volumes
    
            jmp    #loop
    
    minval  long   $C000_0000
    maxval  long   $3FFF_FFFF
    offset  long   $8000_0000
    

    Andy
  • Jack BuffingtonJack Buffington Posts: 115
    edited 2010-09-04 20:24
    I don't have an answer for you but have you tried playing one WAV file and adding in a sine wave that you can just store all of the samples for one wavelength in RAM or just look them up from the ROM? If that works OK, your problem might be an issue with the SD card/FSRW not being able to keep up. I guess that you could try having both buffer objects read from the SD card and only play one of them (try the other one too) to see if the SD card/FSRW is at fault.

    As far as how to mix two audio files together, that one sort of stumps me too. I've thought about that one a few times in the past and didn't come up with an answer. Maybe it has something to do with simply watching the resulting sound levels and if they exceed a certain value within a group of samples(like 30ms worth or so) then you increment down the volume. If it hasn't exceeded the threshold for a group of samples then you let the volume rise unless it is at full volume already. It would sort of be like auto gain control. I would try adjusting the volume to the user's setting after this process.

    I'd love to hear what you end up doing.
  • bradharperbradharper Posts: 64
    edited 2010-09-06 04:02
    Andy, the code you posted works well. I *really* appreciate your insight.

    I now have the signals mixing fluidly, but seems I'm encountering the issue that others have faced with consistently opening and buffering two files simultaneously - at least that's my guess at present.

    I'm seeing a failure rate on the secondary buffer about every third read where either the file never opens, or it opens and kills the other buffer. Oddly enough, sometimes I'll hear the failed buffer play 10-15 seconds later...

    I'll keep investigating, but as far as the original topic, the samples are now mixing clearly. I'll post in the OBEX when I get everything cleaned up and presentable.
  • JonnyMacJonnyMac Posts: 9,209
    edited 2010-09-06 16:21
    If it is a timing thing you may want to limit your sample rate to 22.050kHz; most people won't notice a fidelity change and it will give you more time to read one buffer while the other is being emptied.
  • lonesocklonesock Posts: 917
    edited 2010-09-07 07:19
    bradharper wrote: »
    Andy, the code you posted works well. I *really* appreciate your insight.

    I now have the signals mixing fluidly, but seems I'm encountering the issue that others have faced with consistently opening and buffering two files simultaneously - at least that's my guess at present.

    I'm seeing a failure rate on the secondary buffer about every third read where either the file never opens, or it opens and kills the other buffer. Oddly enough, sometimes I'll hear the failed buffer play 10-15 seconds later...

    I'll keep investigating, but as far as the original topic, the samples are now mixing clearly. I'll post in the OBEX when I get everything cleaned up and presentable.
    Out of curiosity, are you opening both files at the same time using multiple copies of the FSRW object? Or are you opening the 1st file, reading, closing, opening the 2nd, reading, etc.? If the 1st, are both files being read from the same cog, or from different ones?

    Jonathan
  • bradharperbradharper Posts: 64
    edited 2010-09-07 09:46
    lonesock wrote: »
    Out of curiosity, are you opening both files at the same time using multiple copies of the FSRW object?

    Yes, using two copies, uniquely named, each reading from their own cog.

    After a bit more debugging, seems that when the secondary buffer fails, it will either return -1 from fsrw.popen() or open, but derive incoherent values from the header for the channel, sample rate, bit-depth fields.
  • lonesocklonesock Posts: 917
    edited 2010-09-07 10:03
    Gotcha. The "from their own cog" is most likely the issue. There is only one low-level driver cog handling all requests, and the calling code can get confused if multiple requests come in at the same time (there is only 1 "done" flag, and each cog is polling it). It should be easy to to put a lock on the actual read/write PUB routines inside safe_spi. I can look at that later today, if you haven't already beaten me to it by that point [8^)

    Jonathan
  • bradharperbradharper Posts: 64
    edited 2010-09-08 04:15
    @Jonathan, sounds like I need to look into the topic of locks on the Propeller... appreciate the advice. Lemme know if you implement that change by chance. And, by the way, I share your same free time "problem"... ;]

    @Jon, 22kHz does help some, and the lower sample rate is perfectly adequate in this application.

    Thanks guys.
  • bradharperbradharper Posts: 64
    edited 2010-09-23 18:00
    According to Jonathan's advice, I added a lock around the business portion of safe_spi.readblock() method and I'm now able to cleanly open, read and mix multiple audio files seamlessly. The audio sounds great. ;)
    PUB readblock( block_index, buffer_address )
      if SPI_engine_cog == 0
        abort ERR_SPI_ENGINE_NOT_RUNNING
      if (buffer_address & 3)
        abort ERR_BLOCK_NOT_LONG_ALIGNED
      [b]repeat until not lockset(read_lock_id)[/b]
      SPI_block_index := block_index
      SPI_buffer_address := buffer_address
      SPI_command := "r"
      repeat while SPI_command == "r"
      [b]lockclr(read_lock_id)[/b]
      if SPI_command < 0
        abort SPI_command
    

    I appreciate everyone's time.
Sign In or Register to comment.