/'===============================================================================================
 Granular Synthesis example.
 Copyright (c), Firelight Technologies Pty, Ltd 2004-2011.

 This example shows how you can play a string of sounds together without gaps, using the setDelay
 command.
 The basic operation is:
 1. play 2 sounds initially at the same time, the first sound immediately, and the 2nd sound
    with a delay calculated by the length of the first sound.
 2. call setDelay to initiate the delayed playback.  setDelay is sample accurate.  setDelay uses
    -output- samples as the time frame, not source samples.  These samples are a fixed amount
    per second regardless of the source sound format, for example, 48000 samples per second if 
    FMOD is initialized to 48khz output.
 3. Output samples are calculated from source samples with a simple source->output sample rate 
    conversion. ie
        sound_length *= outputrate   
        sound_length /= (int)sound_frequency
 4. When the first sound finishes, the second one should have automatically started.  This is
    a good oppurtunity to queue up the next sound.  Repeat step 2.
 5. Make sure the framerate is high enough to queue up a new sound before the other one finishes
    otherwise you will get gaps.
 
 These sounds are not limited by format, channel count or bit depth like the realtimestitching
 example is, and can also be modified to allow for overlap, by reducing the delay from the first
 sound playing to the second by the overlap amount.
 
 #define USE_STREAMS = use 2 stream instances, create and free them while they play.
 '#define USE_STREAMS = use 6 static wavs, all loaded into memory.
 

==============================================================================================='/
#include "windows.bi"
#include "crt.bi"
	
#include "fmodex/fmodex.bi"
#include "fmodex/fmod_errors.bi"

sub ERRCHECK(byval result as FMOD_RESULT)
    if (result <> FMOD_OK) then
        printf(!"FMOD error! (%d) %s\n", result, FMOD_ErrorString(result))
        end -1
    end if
end sub

'#define USE_STREAMS

dim shared as FMOD_SYSTEM ptr gSystem

#ifdef USE_STREAMS
    #define NUMSOUNDS 3                         /' Use some longer sounds, free and load them on the fly. '/
    dim shared as FMOD_SOUND       ptr sound(2-1) = { 0,0 }       /' 2 streams active, double buffer them. '/
    dim shared as const zstring       ptr soundname(NUMSOUNDS) = _
    { _
        @"media/c.ogg", _
        @"media/d.ogg", _
        @"media/e.ogg" _
    }
#else
    #define NUMSOUNDS 6                                     /' These sounds will be loaded into memory statically. '/
    dim shared as FMOD_SOUND       ptr sound(NUMSOUNDS) = { 0,0,0,0,0,0 }   /' 8 sounds active, one for each wav. '/
    dim shared as const zstring       ptr soundname(NUMSOUNDS) = _
    { _
        @"media/granular/truck_idle_off_01.wav", _
        @"media/granular/truck_idle_off_02.wav", _
        @"media/granular/truck_idle_off_03.wav", _
        @"media/granular/truck_idle_off_04.wav", _
        @"media/granular/truck_idle_off_05.wav", _
        @"media/granular/truck_idle_off_06.wav" _
    }
#endif

sub set_paused(byval channel as FMOD_CHANNEL ptr ptr, byval paused as integer)
    dim as FMOD_RESULT result = any
    dim as uinteger blocksize = any
    
    result = FMOD_System_GetDSPBufferSize(gSystem, @blocksize, 0)
    ERRCHECK(result)

    if (paused = 0) then
        dim as uinteger pausestart_hi = 0, pausestart_lo = 0

        FMOD_System_GetDSPClock(gSystem, @pausestart_hi, @pausestart_lo)
        
        FMOD_64BIT_ADD(pausestart_hi, pausestart_lo, 0, blocksize * 2)   /' Into the future by 2 mixer blocks. '/
        printf(!"\npause BOTH at %d \n", pausestart_lo)
       
        /' Make them both pause at exactly the same tick.  Mute them both to avoid a click as well. '/
        FMOD_Channel_SetMute(channel[0], TRUE)
        FMOD_Channel_SetDelay(channel[0], FMOD_DELAYTYPE_DSPCLOCK_PAUSE, pausestart_hi, pausestart_lo)
        FMOD_Channel_SetMute(channel[1], TRUE)
        FMOD_Channel_SetDelay(channel[1], FMOD_DELAYTYPE_DSPCLOCK_PAUSE, pausestart_hi, pausestart_lo)
    else
        dim as uinteger syshi = any, syslo = any
        dim as integer count = any

        FMOD_System_GetDSPClock(gSystem, @syshi, @syslo)

        printf(!"\nunpause BOTH at %d\n", syslo)
        
        for count = 0 to 2-1
            dim as uinteger starttime_hi = any, starttime_lo = any
            dim as uinteger pausetime_hi = 0, pausetime_lo = 0
            dim as uinteger hi = syshi, lo = syslo
            
            FMOD_Channel_GetDelay(channel[count], FMOD_DELAYTYPE_DSPCLOCK_PAUSE, @pausetime_hi, @pausetime_lo)
            FMOD_Channel_GetDelay(channel[count], FMOD_DELAYTYPE_DSPCLOCK_START, @starttime_hi, @starttime_lo)

            FMOD_64BIT_ADD(hi, lo, 0, blocksize * 2)                   /' Push operation into the future by 2 mixer blocks so it doesnt conflict with mixer. '/
            if (starttime_lo > pausetime_lo) then                           /' Was already playing, unpause immediately. '/
                FMOD_64BIT_ADD(hi, lo, starttime_hi, starttime_lo)     /' Push forward the delayed start by the gap between starting and pausing '/
                FMOD_64BIT_SUB(hi, lo, pausetime_hi, pausetime_lo)     /' Push forward the delayed start by the gap between starting and pausing '/
            end if
            printf(!"restart %d at %d\n", count, lo)
            FMOD_Channel_SetDelay(channel[count], FMOD_DELAYTYPE_DSPCLOCK_PAUSE, 0, 0)
            FMOD_Channel_SetDelay(channel[count], FMOD_DELAYTYPE_DSPCLOCK_START, hi, lo)
            FMOD_Channel_SetMute(channel[count], FALSE)
            FMOD_Channel_SetPaused(channel[count], FALSE)
        next
    end if
end sub


function queue_next_sound(byval outputrate as integer, byval playingchannel as FMOD_CHANNEL ptr, byval newindex as integer, byval slot as integer) as FMOD_CHANNEL ptr
    dim as FMOD_RESULT result = any
    dim as FMOD_CHANNEL ptr newchannel = any
    dim as FMOD_SOUND ptr newsound = any
    
    #ifdef USE_STREAMS                                  /' Create a new stream '/
dim as     FMOD_CREATESOUNDEXINFO info

    info.cbsize = sizeof(FMOD_CREATESOUNDEXINFO)
    info.suggestedsoundtype = FMOD_SOUND_TYPE_OGGVORBIS
    result = FMOD_System_CreateStream(gSystem, soundname(newindex), FMOD_IGNORETAGS or FMOD_LOWMEM, @info, @sound(slot))
    ERRCHECK(result)
    newsound = sound(slot)
    #else                                               /' Use an existing sound that was passed into us '/
    newsound = sound(newindex)
    #endif
    
    result = FMOD_System_PlaySound(gSystem, FMOD_CHANNEL_FREE, newsound, 1, @newchannel)
    ERRCHECK(result)
      
    result = FMOD_Channel_SetSpeakerMix(newchannel, 1,1,1,1,1,1,1,1)
    ERRCHECK(result)
           
    
    if (playingchannel) then
        dim as uinteger hi = 0, lo = 0, sound_length = any
        dim as float sound_frequency = any
        dim as FMOD_SOUND ptr playingsound = any
        
        /'
            Get the start time of the playing channel.
        '/
        result = FMOD_Channel_GetDelay(playingchannel, FMOD_DELAYTYPE_DSPCLOCK_START, @hi, @lo)
        ERRCHECK(result)
        
        printf(!"playing sound started at %d\n", lo)
        
        /'
            Grab the length of the playing sound, and its frequency, so we can caluate where to place the new sound on the time line.
        '/
        result = FMOD_Channel_GetCurrentSound(playingchannel, @playingsound)
        ERRCHECK(result)
        result = FMOD_Sound_GetLength(playingsound, @sound_length, FMOD_TIMEUNIT_PCM)
        ERRCHECK(result)
        result = FMOD_Channel_GetFrequency(playingchannel, @sound_frequency)
        ERRCHECK(result)
        
        /' 
            Now calculate the length of the sound in 'output samples'.  
            Ie if a 44khz sound is 22050 samples long, and the output rate is 48khz, then we want to delay by 24000 output samples.
        '/
        sound_length *= outputrate   
        sound_length /= sound_frequency
        
        FMOD_64BIT_ADD(hi, lo, 0, sound_length)  /' Add output rate adjusted sound length, to the clock value of the sound that is currently playing '/
            
        result = FMOD_Channel_SetDelay(newchannel, FMOD_DELAYTYPE_DSPCLOCK_START, hi, lo)      /' Set the delay of the new sound to the end of the old sound '/
        ERRCHECK(result)
    end if
    
    scope
        dim as uinteger hi = 0, lo = 0
        dim as float value = any, variation = any
        
        /'
            Randomize pitch/volume to make it sound more realistic / random.
        '/
        FMOD_Channel_GetFrequency(newchannel, @value)
        variation = (((rnd mod 10000) / 5000.0f) - 1.0f) /' -1.0 to +1.0 '/
        value *= (1.0f + (variation * 0.02f))                    /' @22khz, range fluctuates from 21509 to 22491 '/
        FMOD_Channel_SetFrequency(newchannel, value)

        FMOD_Channel_GetVolume(newchannel, @value)
        variation = ((rnd mod 10000) / 10000.0f)         /'  0.0 to 1.0 '/
        value *= (1.0f - (variation * 0.2f))                     /'  0.8 to 1.0 '/
        FMOD_Channel_SetVolume(newchannel, value)
                
        FMOD_Channel_GetDelay(newchannel, FMOD_DELAYTYPE_DSPCLOCK_START, @hi, @lo)
        printf(!"new sound to start at %d (slot %d)\n", lo, slot)
    end scope
        
    result = FMOD_Channel_SetPaused(newchannel, FALSE)
    ERRCHECK(result)
       
function = newchannel
end function

' program start
randomize timer,2
    dim as FMOD_CHANNEL    ptr channel(2-1) = { 0,0 }
    dim as FMOD_RESULT       result = any
    dim as string               key
    dim as integer outputrate = any, slot = 0, count = any, numdrivers = any
    dim as uinteger      version = any
    
    printf(!"=============================================================================\n")
    printf(!"Granular Synthesis SetDelay example.\n")
    printf(!"Copyright (c) Firelight Technologies 2004-2011.\n")
    printf(!"=============================================================================\n")
    printf(!"\n")
    printf(!"TOGGLE #define USE_STREAMS ON/OFF TO SWITCH BETWEEN STREAMS/STATIC SAMPLES.\n")
    printf(!"Press space to pause, Esc to quit\n")
    printf(!"\n")
    
    /'
        ===============================================================================================================
        RECOMMENDED STARTUP SEQUENCE BEGIN
        ===============================================================================================================
    '/

    result = FMOD_System_Create(@gSystem)
    ERRCHECK(result)
    
    result = FMOD_System_GetVersion(gSystem, @version)
    ERRCHECK(result)

    if (version < FMOD_VERSION) then
        printf(!"Error!  You are using an old version of FMOD %08x.  This program requires %08x\n", version, FMOD_VERSION)
        end 0
    end if
    
    result = FMOD_System_GetNumDrivers(gSystem, @numdrivers)
    ERRCHECK(result)

    if (numdrivers = 0) then
        result = FMOD_System_SetOutput(gSystem, FMOD_OUTPUTTYPE_NOSOUND)
        ERRCHECK(result)
    else
        dim as FMOD_CAPS caps = any
        dim as FMOD_SPEAKERMODE speakermode = any
        dim as zstring * 256 sname
        
        result = FMOD_System_GetDriverCaps(gSystem, 0, @caps, 0, @speakermode)
        ERRCHECK(result)

        result = FMOD_System_SetSpeakerMode(gSystem, speakermode)       /' Set the user selected speaker mode. '/
        ERRCHECK(result)

        if (caps and FMOD_CAPS_HARDWARE_EMULATED) then            /' The user has the 'Acceleration' slider set to off!  This is really bad for latency!. '/
            result = FMOD_System_SetDSPBufferSize(gSystem, 1024, 10)
            ERRCHECK(result)
        end if

        result = FMOD_System_GetDriverInfo(gSystem, 0, @sname, 256, 0)
        ERRCHECK(result)

        if (strstr(sname, "SigmaTel")) then  /' Sigmatel sound devices crackle for some reason if the format is PCM 16bit.  PCM floating point output seems to solve it. '/
            result = FMOD_System_SetSoftwareFormat(gSystem, 48000, FMOD_SOUND_FORMAT_PCMFLOAT, 0,0, FMOD_DSP_RESAMPLER_LINEAR)
            ERRCHECK(result)
        end if
    end if

    result = FMOD_System_Init(gSystem, 100, FMOD_INIT_NORMAL, 0)
    if (result = FMOD_ERR_OUTPUT_CREATEBUFFER) then        /' Ok, the speaker mode selected isn't supported by this soundcard.  Switch it back to stereo... '/
        result = FMOD_System_SetSpeakerMode(gSystem, FMOD_SPEAKERMODE_STEREO)
        ERRCHECK(result)
            
        result = FMOD_System_Init(gSystem, 100, FMOD_INIT_NORMAL, 0)/' ... and re-init. '/
        ERRCHECK(result)
    end if
    
    /'
        ===============================================================================================================
        RECOMMENDED STARTUP SEQUENCE END
        ===============================================================================================================
    '/
        
    result = FMOD_System_GetSoftwareFormat(gSystem, @outputrate, 0,0,0,0,0)
    ERRCHECK(result)   
   
#ifndef USE_STREAMS
    for count = 0 to NUMSOUNDS-1
        result = FMOD_System_CreateSound(gSystem, soundname(count), FMOD_IGNORETAGS, 0, @sound(count))
        ERRCHECK(result)
    next
#endif

    /'
        Kick off the first 2 sounds.  First one is immediate, second one will be triggered to start after the first one.
    '/
    channel(slot) = queue_next_sound(outputrate, channel(1-slot), rnd mod NUMSOUNDS, slot)
    slot = 1-slot  /' flip '/
    channel(slot) = queue_next_sound(outputrate, channel(1-slot), rnd mod NUMSOUNDS, slot)
    slot = 1-slot  /' flip '/

    /'
        Main loop.
    '/
    do
key = inkey
        dim as integer isplaying = any
        static as integer paused = 0

            select case key
                case " " :
                    set_paused(@channel(0), paused)
                    paused xor=1
            end select

        FMOD_System_Update(gSystem)

        /'
            Replace the sound that just finished with a new sound, to create endless seamless stitching!
        '/
        result = FMOD_Channel_IsPlaying(channel(slot), @isplaying)
        if (isplaying = 0) and (paused = 0) then
            printf(!"sound %d finished, start a new one\n", slot)
            #ifdef USE_STREAMS
            /' 
                Release the sound that isn't playing any more. 
            '/
            result = FMOD_Sound_Release(sound(slot))       
            ERRCHECK(result)
            sound(slot) = 0
            #endif
   
            /'
                Replace sound that just ended with a new sound, queued up to trigger exactly after the other sound ends.
            '/
            channel(slot) = queue_next_sound(outputrate, channel(1-slot), rnd mod NUMSOUNDS, slot)
            slot = 1-slot  /' flip '/
        end if
        
        Sleep(10)  /' If you wait too long, ie longer than the length of the shortest sound, you will get gaps. '/

    loop while (key <> chr(27))

    printf(!"\n")

    for count = 0 to sizeof(sound) / sizeof(sound(0))-1
        if (sound(count)) then
            FMOD_Sound_Release(sound(count))
        end if
    next
    
    /'
        Shut down
    '/
    result = FMOD_System_Release(gSystem)
    ERRCHECK(result)
