Shop OBEX P1 Docs P2 Docs Learn Events
Seamless WAV restarting — Parallax Forums

Seamless WAV restarting

WIFLIWIFLI Posts: 4
edited 2015-02-16 20:51 in Propeller 1
Hi all,

Long time lurker, first time poster! I'm looking to use Kye's KISS WAV Player Driver to play WAV files for an audio project, and I need to be able to seamlessly restart playback (while playing a file, having playback restart from the beginning of the file before playback has completed) fairly rapidly (ideally within 50ms or less, within 100ms at a minimum). I had thought it would be as simple as changing the sample position to its intial value so the object would refill buffers from the beginning of the file, but that doesn't seem to do the trick.

I get the feeling I'm missing something conceptual here, and so far I've gotten nowhere trying to figure out what's wrong. Any thoughts on what I'm missing? Is there a better approach to what I'm trying to accomplish? (I haven't posted code because it's long and it's a well-known object, would be happy to so so though if it makes it easier)

Comments

  • Clive WakehamClive Wakeham Posts: 152
    edited 2012-12-22 00:34
    Considering that the KISS WAV Player Driver is pulling the data from a MMC chip/card, how much time does it take to initialize?
  • WIFLIWIFLI Posts: 4
    edited 2012-12-22 01:18
    Considering that the KISS WAV Player Driver is pulling the data from a MMC chip/card, how much time does it take to initialize?

    Measuring the time from when I first call the wavplayer to the time the file begins playing, I get about 229 ms. This is also measured on a loop which lets the file play to completion; so far I've been unable to stop the cog prior to file completion and restart it without problems.
  • RaymanRayman Posts: 14,652
    edited 2012-12-22 03:18
    I'm not familiar with that particalar wav object, but can't you just exit from the playback loop and start over from the beginning?
  • WIFLIWIFLI Posts: 4
    edited 2012-12-22 03:53
    Rayman wrote: »
    I'm not familiar with that particalar wav object, but can't you just exit from the playback loop and start over from the beginning?

    I assume that's the way to do it; the problem is I'm not sure how to do this in ASM. Below is the code for the player:
    ' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    '                       DAC Driver
    ' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
                            org     0
    ' //////////////////////Initialization/////////////////////////////////////////////////////////////////////////////////////////
    initialization          mov     ctra,               leftCounterSetup           ' Setup counter modes to duty cycle mode.
                            mov     ctrb,               rightCounterSetup          '
                            mov     frqa,               longAdjust                 '
                            mov     frqb,               longAdjust                 '
                            mov     dira,               outputMask                 '
                            mov     playerPointer,      dataBlockAddress           '
                            rdlong  timeCounter,        par                        ' Setup timing.
                            add     timeCounter,        cnt                        '
    ' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    '                       Player                                                                                 
    ' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    outerLoop               rdlong  buffer,             samplePositionAddress wz   ' Enable playback playback given samples. 
                            muxnz   playerMode,         #4                         '
                                       
                            rdword  buffer,             numberOfChannelsAddress    ' Setup number of channels.
                            cmp     buffer,             #1 wz                      '
                            muxz    playerMode,         #1                         '
                            rdword  buffer,             bitsPerSampleAddress       ' Setup bits per channel.
                            cmp     buffer,             #8 wz                      '
                            muxz    playerMode,         #2                         '
                            mov     counter,            #128                       ' Setup loop counter.
                            test    playerMode,         #1 wc                      '
    if_c                    shl     counter,            #1                         '
                            test    playerMode,         #2 wc                      '
    if_c                    shl     counter,            #1                         '
    ' //////////////////////Inner Loop/////////////////////////////////////////////////////////////////////////////////////////////
    innerLoop               rdlong  buffer,             par                        ' Wait until next sample output period.
                            waitcnt timeCounter,        buffer                     '
                            rdbyte  buffer,             stopedOrStartedAddress wz  ' If stopped loop continously.
    if_nz                   mov     frqa,               longAdjust                 '
    if_nz                   mov     frqb,               longAdjust                 '
    if_nz                   jmp     #innerLoop                                     '
                            movs    signedLongOutput,   #leftValueAddress          ' Get and output value.
                            movs    multiplicand,       #leftVolumeAddress         '
                            movs    unsignedLongOutput, #ditherOutLeftAddress      '
                            call    #decode                                        '
                            mov     frqa,               sampleBuffer               '
                            test    playerMode,         #1 wc                      ' Check number of channels.
                            test    playerMode,         #2 wz                      '
    if_c_and_nz             sub     playerPointer,      #1                         '
    if_c_and_z              sub     playerPointer,      #2                         '
                            movs    signedLongOutput,   #rightValueAddress         ' Get and output value.
                            movs    multiplicand,       #rightVolumeAddress        '
                            movs    unsignedLongOutput, #ditherOutRightAddress     ' 
                            call    #decode                                        '
                            mov     frqb,               sampleBuffer               '
                            rdlong  buffer,             samplePositionAddress      ' Decrement position.
                            cmpsub  buffer,             #1 wc, wz                  '
    if_c                    wrlong  buffer,             samplePositionAddress      '
    if_z                    andn    playerMode,         #4                         ' Disable playback given samples.    
                            test    playerMode,         #4 wc                      ' Playback begun.
    if_c                    mov     buffer,             #$FF                       '
    if_c_and_nz             wrbyte  buffer,             beginOrEndAddress          '
    if_z                    wrbyte  buffer,             beginOrEndAddress          ' Playback be done.
       
                            djnz    counter,            #innerLoop                 ' Loop.
    ' //////////////////////Outer Loop/////////////////////////////////////////////////////////////////////////////////////////////
                            rdword  buffer,             callePointerAddress wz     ' Switch data block pointer.
                            sumz    buffer,             #1                         '
                            wrword  buffer,             callePointerAddress        '
    if_nz                   mov     playerPointer,      dataBlockAddress           '
                            jmp     #outerLoop                                     ' Loop.
    ' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    '                       Decode Value
    ' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    decode                  test    playerMode,         #2 wc                      ' Read data depending on size.
    if_c                    rdbyte  multiplyBuffer,     playerPointer              ' Read a byte or a word.   
    if_c                    add     playerPointer,      #1                         '
    if_nc                   rdword  multiplyBuffer,     playerPointer              '
    if_nc                   add     playerPointer,      #2                         '
    if_c                    sub     multiplyBuffer,     #128                       ' Sign extend.
    if_c                    shl     multiplyBuffer,     #24                        '
    if_nc                   shl     multiplyBuffer,     #16                        '
                            sar     multiplyBuffer,     #16                        '
                            test    playerMode,         #4 wz                      ' Zero playback given samples.  
    if_z                    mov     multiplyBuffer,     #0                         '
                            
    signedLongOutput        wrlong  multiplyBuffer,     0                          ' Update main memory.
    multiplicand            rdword  multiplyCounter,    0                          ' Setup inputs.
                            mov     sampleBuffer,       #0                         '
                            abs     multiplyBuffer,     multiplyBuffer wc          ' Backup sign.
                            rcr     sampleBuffer,       #1 wz, nr                  '
    multiplyLoop            shr     multiplyCounter,    #1 wc                      ' Preform multiplication.
    if_c                    add     sampleBuffer,       multiplyBuffer             '
                            shl     multiplyBuffer,     #1                         '
                            tjnz    multiplyCounter,    #multiplyLoop              '
                            negnz   sampleBuffer,       sampleBuffer               ' Restore sign and center value.
    unsignedLongOutput      wrlong  sampleBuffer,       0                          '                        
                            add     sampleBuffer,       longAdjust                 '
    decode_ret              ret                                                    ' Return.
    ' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    '                       Data
    ' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    longAdjust              long    $80_00_00_00                                   ' Centers output.
    ' //////////////////////Configuration Settings/////////////////////////////////////////////////////////////////////////////////
    leftCounterSetup        long    0
    rightCounterSetup       long    0
    outputMask              long    0
    ' //////////////////////Addresses//////////////////////////////////////////////////////////////////////////////////////////////
    leftValueAddress        long    0
    rightValueAddress       long    0
    samplePositionAddress   long    0
    dataBlockAddress        long    0
    callePointerAddress     long    0
    numberOfChannelsAddress long    0
    bitsPerSampleAddress    long    0
    leftVolumeAddress       long    0
    rightVolumeAddress      long    0
    stopedOrStartedAddress  long    0
    beginOrEndAddress       long    0
    ditherOutLeftAddress    long    0
    ditherOutRightAddress   long    0 
    ' //////////////////////Run Time Variables/////////////////////////////////////////////////////////////////////////////////////
    buffer                  res     1
    counter                 res     1
    playerPointer           res     1
    playerMode              res     1
    sampleBuffer            res     1
    timeCounter             res     1
    multiplyBuffer          res     1
    multiplyCounter         res     1
    ' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
                            fit     496
    
    

    Despite the excellent documentation, I can't seem to figure out how to make this work. Should I be looking to reset samplePositionAddress to the initial value and jump back to outerLoop? If so, how would I pass a command from my main program to the wav-playing cog to let it know to loop? (I'm thinking I'd cmp the command register to a preset literal to trigger the restart, but not sure if that's the best way).

    My apologies if the above code isn't clear enough without the spin...like I said above, I think it's a conceptual problem I have with my lack of skill in ASM. If you have an object you're more familiar with and could better use as an example, I'd be happy to try to follow along...it seems I couldn't understand much less about a new object than I do about this one! :tongue:
  • KyeKye Posts: 2,200
    edited 2012-12-22 06:58
    Hi, I'm on vacation right now, so, I really can't help you right now. Drop me an email later on Monday and I'll send you an object that does what you need.

    Thanks,
  • WIFLIWIFLI Posts: 4
    edited 2012-12-22 11:23
    Kye wrote: »
    Hi, I'm on vacation right now, so, I really can't help you right now. Drop me an email later on Monday and I'll send you an object that does what you need.

    Thanks,

    Wow, can't ask for more than that...will do Kye, thanks!
  • KaiNKaiN Posts: 4
    edited 2015-02-11 19:21
    WIFLI wrote: »
    Wow, can't ask for more than that...will do Kye, thanks!

    Sorry to reopen this topic, but I have a similar project that needs a seamless WAV player. Can someone enlighten me on how this can be done? I am currently using Kye's amazing KISS object.
  • JonnyMacJonnyMac Posts: 9,105
    edited 2015-02-12 09:24
    From a human standpoint, closing and re-opening the WAV file from the beginning is seamless. I do this all the time in my own WAV player (which uses FSRW for speed). In a current client project, we're taking a single gunshot sound and restarting it every 60ms to sound like a machine gun. The client was thrilled that his laser-tag controller can "shoot" IR bullets with a sound effect at the rate of 1000 rounds per minute (we tested faster, but this is the spec of the product). That test was done on a board with a 5MHz crystal (80MHz); we've just changed that to 6.5MHz (104MHz) and expect even better performance.
  • KaiNKaiN Posts: 4
    edited 2015-02-12 17:03
    JonnyMac wrote: »
    From a human standpoint, closing and re-opening the WAV file from the beginning is seamless. I do this all the time in my own WAV player (which uses FSRW for speed). In a current client project, we're taking a single gunshot sound and restarting it every 60ms to sound like a machine gun. The client was thrilled that his laser-tag controller can "shoot" IR bullets with a sound effect at the rate of 1000 rounds per minute (we tested faster, but this is the spec of the product). That test was done on a board with a 5MHz crystal (80MHz); we've just changed that to 6.5MHz (104MHz) and expect even better performance.

    Is your WAV player in the OBEX? I've tried queuing multiple sounds using the KISS driver, but there are noticeable pauses between each play.

    Thanks for the help!
  • JonnyMacJonnyMac Posts: 9,105
    edited 2015-02-12 18:01
    No, but I've attached demo that provides you with that object and demonstrates what I mentioned. The archive contains a file called SHOT.WAV; move this to your uSD card and adjust the pins in the code as required (demo works on the Activity Board).

    My object doesn't have a queing feature for multiple files -- I have never needed that, and it would be easy to code for an application that does. That said, the play() method has a counts parameter so you can play the same file any number of times. If you specify 0 as the count parameter, it gets promoted to POSX -- we figured 2.14 billion plays counts as forever. BTW, this code is the audio engine in the AP-16+ which is used in haunted houses, museums, Disneyland, and just last year Legoland selected the AP-16+ plus the HC-8+ (both from EFX-TEK) for their audio-animatronic displays across the world.
  • KaiNKaiN Posts: 4
    edited 2015-02-13 08:13
    It works! Thank you so much, I really appreciate it. :smile:
  • KaiNKaiN Posts: 4
    edited 2015-02-16 19:49
    Quick question: I got the player working and the repeating functioning, but I was wondering if there is 44kHz stereo file support?

    Thank you for all the help.
  • JonnyMacJonnyMac Posts: 9,105
    edited 2015-02-16 20:51
    Yes. I've even done 48kHz stereo with a 6.25 MHz crystal.
Sign In or Register to comment.