/'===============================================================================================
 Record to disk example
 Copyright (c), Firelight Technologies Pty, Ltd 2004-2011.

 This example shows how to record continuously, and play back the same data as closely to 
 the record cursor as possible without stuttering.
 
 NOTE - This setup is about 250ms latency to be 'safe', because combinations of operating system
 and sound card driver can cause stuttering if the settings are set too low.  To reduce latency
 on playback, use #define LOWLATENCY.   Currently it is commented out.
==============================================================================================='/
#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 LOWLATENCY      /' Only really suited to Windows Vista/7.  XP/Directsound should avoid this. '/

#define RECORDRATE      8000

#ifdef LOWLATENCY
    #define LATENCY         ((RECORDRATE * 20) / 1000)   /' 20 = 20ms '/
    #define DRIFTTHRESHOLD  ((RECORDRATE * 10) / 1000)   /' 10 = 10ms '/
#else
    #define LATENCY         ((RECORDRATE * 50) / 1000)   /' 50 = 50ms '/
    #define DRIFTTHRESHOLD  ((RECORDRATE * 25) / 1000)   /' 25 = 25ms '/
#endif

' program start
    dim as FMOD_SYSTEM           ptr fsystem  = 0
    dim as FMOD_SOUND            ptr sound   = 0
    dim as FMOD_CHANNEL          ptr channel = 0
    dim as FMOD_DSP              ptr dsp     = 0
    dim as FMOD_RESULT            result = any
    dim as FMOD_CREATESOUNDEXINFO exinfo
    dim as FMOD_SPEAKERMODE       speakermode = any
    dim as FMOD_CAPS              caps = any
    dim as string                    key
    dim as integer numdrivers = any
    dim as uinteger           version    = any
    dim as uinteger           datalength = 0, soundlength = any
    dim as zstring * 256                   sname = any
    dim as uinteger           adjustedlatency = any

    /'
        Create a System object and initialize.
    '/
    result = FMOD_System_Create(@fsystem)
    ERRCHECK(result)

    result = FMOD_System_GetVersion(fsystem, @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

    /' 
        System initialization (recommended startup sequence)
    '/
    result = FMOD_System_GetNumDrivers(fsystem, @numdrivers)
    ERRCHECK(result)

    if (numdrivers = 0) then
        result = FMOD_System_SetOutput(fsystem, FMOD_OUTPUTTYPE_NOSOUND)
        ERRCHECK(result)
    else
        result = FMOD_System_GetDriverCaps(fsystem, 0, @caps, 0, @speakermode)
        ERRCHECK(result)

        result = FMOD_System_SetSpeakerMode(fsystem, 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(fsystem, 1024, 10)
            ERRCHECK(result)
#ifdef LOWLATENCY
        else
            result = FMOD_System_SetDSPBufferSize(fsystem, 256, 4)
        end if
#endif
        result = FMOD_System_GetDriverInfo(fsystem, 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(fsystem, 48000, FMOD_SOUND_FORMAT_PCMFLOAT, 0,0, FMOD_DSP_RESAMPLER_LINEAR)
            ERRCHECK(result)
        end if
    end if

    result = FMOD_System_Init(fsystem, 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(fsystem, FMOD_SPEAKERMODE_STEREO)
        ERRCHECK(result)
            
        result = FMOD_System_Init(fsystem, 100, FMOD_INIT_NORMAL, 0)/' ... and re-init. '/
        ERRCHECK(result)
    end if
    /' 
        System initialization complete (recommended startup sequence)
    '/
 

    /'
        Create user sound to record into.  Set it to loop for playback.
    '/
    exinfo.cbsize           = sizeof(FMOD_CREATESOUNDEXINFO)
    exinfo.numchannels      = 1
    exinfo.format           = FMOD_SOUND_FORMAT_PCM16
    exinfo.defaultfrequency = RECORDRATE
    exinfo.length           = exinfo.defaultfrequency * sizeof(short) * exinfo.numchannels * 5 /' 5 second buffer, doesnt really matter how big this is, but not too small of course. '/
    
    result = FMOD_System_CreateSound(fsystem, 0, FMOD_2D or FMOD_SOFTWARE or FMOD_LOOP_NORMAL or FMOD_OPENUSER, @exinfo, @sound)
    ERRCHECK(result)

    printf(!"========================================================================\n")
    printf(!"Record with realtime playback example.\n")
    printf(!"Copyright (c) Firelight Technologies 2004-2011.\n")
    printf(!"\n")
    printf(!"Try #define LOWLATENCY to reduce latency for more modern machines!\n")
    printf(!"========================================================================\n")
    printf(!"\n")
    printf(!"Press any key to start recording.  Playback will start %d samples (%d ms) later.\n", LATENCY, LATENCY * 1000 / RECORDRATE)
    printf(!"\n")

    sleep

    printf(!"Press 'E' to toggle an effect on/off.\n")
    printf(!"Press 'Esc' to quit\n")
    printf(!"\n")

    result = FMOD_System_RecordStart(fsystem, 0, sound, TRUE)
    ERRCHECK(result)

    result = FMOD_Sound_GetLength(sound, @soundlength, FMOD_TIMEUNIT_PCM)
    ERRCHECK(result)

    /'
        Create a DSP effect to play with.
    '/
    result = FMOD_System_CreateDSPByType(fsystem, FMOD_DSP_TYPE_FLANGE, @dsp)
    ERRCHECK(result)
    result = FMOD_DSP_SetParameter(dsp, FMOD_DSP_FLANGE_RATE, 4.0f)
    ERRCHECK(result)
    result = FMOD_DSP_SetBypass(dsp, TRUE)   
    ERRCHECK(result)
    
    adjustedlatency = LATENCY  /' This might change depending on record block size. '/
    
    /'
        Main loop.
    '/
    do
        static as uinteger lastrecordpos = 0, samplesrecorded = 0
        dim as uinteger recordpos = 0, recorddelta = any

key = inkey        
        if (key = "e" or key = "E") then
            dim as integer bypass = any
            FMOD_DSP_GetBypass(dsp, @bypass)
bypass xor= 1
            FMOD_DSP_SetBypass(dsp, bypass)
            if (bypass) then
                dim as FMOD_REVERB_PROPERTIES prop = FMOD_PRESET_CONCERTHALL
                FMOD_System_SetReverbProperties(fsystem, @prop)
            else
                dim as FMOD_REVERB_PROPERTIES prop = FMOD_PRESET_OFF
                FMOD_System_SetReverbProperties(fsystem, @prop)
            end if
            printf(!"\n\n *** TURN DSP EFFECT %s ** \n\n", iif(bypass,@"ON",@"OFF"))
        end if
        
        FMOD_System_GetRecordPosition(fsystem, 0, @recordpos)
        ERRCHECK(result)

        recorddelta = iif(recordpos >= lastrecordpos,recordpos - lastrecordpos,recordpos + soundlength - lastrecordpos)
        samplesrecorded += recorddelta

		if (samplesrecorded >= adjustedlatency and channel = 0) then
		    result = FMOD_System_PlaySound(fsystem, FMOD_CHANNEL_FREE, sound, 0, @channel)
            ERRCHECK(result)
            
            result = FMOD_Channel_AddDSP(channel, dsp, 0)
            ERRCHECK(result)
		end if
				
        if (channel andalso recorddelta) then
            dim as uinteger playrecorddelta = any
            dim as uinteger playpos = 0
            dim as integer adjusting = 0
            dim as float smootheddelta = any
            dim as float dampratio = 0.97f
            static as uinteger minrecorddelta = cast(uinteger, -1)
            
            /'
                If the record driver steps the position of the record cursor in larger increments than the user defined latency value, then we should
                increase our latency value to match.
            '/
            if (recorddelta < minrecorddelta) then
                minrecorddelta = recorddelta
                if (adjustedlatency < recorddelta) then
                    adjustedlatency = recorddelta
                end if
            end if

            FMOD_Channel_GetPosition(channel, @playpos, FMOD_TIMEUNIT_PCM)

            playrecorddelta = iif(recordpos >= playpos,recordpos - playpos,recordpos + soundlength - playpos)
            
	        /'
                Smooth total
            '/
            scope
                static as float total = 0
                
                total = total * dampratio
	            total += playrecorddelta
	            smootheddelta = total * (1.0f - dampratio)
            end scope
           
            if (smootheddelta < adjustedlatency - DRIFTTHRESHOLD or smootheddelta > soundlength / 2) then   /' if play cursor is catching up to record (or passed it), slow playback down '/
                FMOD_Channel_SetFrequency(channel, RECORDRATE - (RECORDRATE / 50)) /' Decrease speed by 2% '/
                adjusting = 1
            ElseIf (smootheddelta > adjustedlatency + DRIFTTHRESHOLD) then   /' if play cursor is falling too far behind record, speed playback up '/
                FMOD_Channel_SetFrequency(channel, RECORDRATE + (RECORDRATE / 50)) /' Increase speed by 2% '/
                adjusting = 2
            else
                FMOD_Channel_SetFrequency(channel, RECORDRATE)          /' Otherwise set to normal rate '/
                adjusting = 0
            end if
            
            printf(!"REC %5d (REC delta %5d) : PLAY %5d, PLAY/REC diff %5d %s\r", recordpos, recorddelta, playpos, cast(integer,smootheddelta), _
iif(adjusting = 1,@"DECREASE SPEED",iif(adjusting = 2,@"INCREASE SPEED",@"              ")))
end if
        
        lastrecordpos = recordpos
        
        FMOD_System_Update(fsystem)

        Sleep(10)

    loop while (key <> chr(27))

    printf(!"\n")

    /'
        Shut down
    '/
    result = FMOD_Sound_Release(sound)
    ERRCHECK(result)

    result = FMOD_System_Release(fsystem)
    ERRCHECK(result)
