#!/bin/bash -e

#####################################################################
#                                                                   #
#                    faust2daisysram generator                          #
#                       (c) Grame, 2020                             #
#                                                                   #
#####################################################################

. faustpath
. faustoptflags
. usage.sh

CXXFLAGS+=" $MYGCCFLAGS"  # So that additional CXXFLAGS can be used
C_INCLUDES="-I $FAUSTINC"
ARCHFILE=$FAUSTARCH/daisy/ex_faust.cpp

# exit if a command fails
set -e

# global option variables
NVOICES=0
MIDI=false
POLY=""
PATCH=false
POD=false
SEED=false
PATCHSM=false
USE_SDRAM=false
USE_SOUNDFILE=false
USE_SD_SOUNDFILE=false
UPLOAD_SD=false
SOUNDFILE_MODE=qspi
SD_DEBUG=false
SR=48000
SRSTR="SAI_48KHZ"
BS=16
SOURCE=false
APP_TYPE=BOOT_NONE
MEM_THRESH=1024
JSON_CONFIG=0
UART=false
RX_PIN=0
TX_PIN=0
POLY_MODE="VOICE_STEALING"

# Serial (UART) control link, set by faust_daisy_parser.py when the DSP uses
# [rx:Dx] / [tx:Dx] control metadata.
SERIAL=false
SERIAL_RX_INPUTS=0
SERIAL_TX_OUTPUTS=0

# Hardware PWM outputs, set by faust_daisy_parser.py when the DSP uses [pwm:PIN].
PWM=false
PWM_OUTPUTS=0

echoHelp ()
{
    echo "faust2daisy [-patch] [-sram] [-qspi] [-sdram] [-mem-thresh] [-midi] [-nvoices <num>] [-sr <num>] [-bs <num>] [-source] [Faust options (-vec -vs 8...)] <file.dsp>"
    echo "Compiles Faust programs to Daisy boards (see https://github.com/grame-cncm/faust/tree/master-dev/architecture/daisy)"
    option
    option -sram "flashing to sram (medium sized programs), requires daisy bootloader"
    option -qspi "flashing to qspi memory (biggest programs), requires daisy bootloader"
    option -seed "Using Daisy seed"
    option -patchsm "Using Daisy Patch submodule"
    option -patch "to compile for 4 ins/outs Patch (knob[1,2,3,4])"
    option -pod "to compile for 2 ins/outs Pod (knob[1,3])"
    option -sdram "to compile using SDRAM for long delay lines/tables etc."
    option "-config-file <filename>" "JSON file to specify mapping of a platform (like pod, patch) to the chip, like pod_spec.json in Faust architecture files for Daisy" 
    option "-mem-thresh <num>" "Memory threshold in bytes above which data is stored on SDRAM, requires -sdram"
    option -midi "Enables MIDI (USB, with default Daisy USB)"
    option -midi-uart "Enables MIDI through UART pins (for Pod for example)"
    option -rx-pin "RX Pin for MIDI UART"
    option -tx-pin "TX Pin for MIDI UART"
    option "-nvoices <num>"
    option "-poly-mode <mode>" "Mode for polyphony, options : <stealing> or <blocking> (defaults to stealing)"
    option "-sr <num>"
    option "-bs <num>"
    option -source
    option -sd "to load soundfiles from the SD card at runtime instead of inlining to QSPI"
    option -sd-debug "to print loaded soundfile info over USB serial at startup"
    option -upload-sd "to upload soundfiles to the SD card (does not build the DSP, just the uploader)"
    option "Faust options"
    echo ""
    exit
}

if [ "$#" -eq 0 ]; then
    echo 'Please, provide a Faust file to process !'
    echo ''
    echoHelp
fi

###########################
# Processing Arguments
###########################

while [ $1 ]
do
    p=$1
    # help
    if [ $p = "-help" ] || [ $p = "-h" ]; then
        echoHelp
    # -nvoices:
    elif [ $p = "-config-file" ]; then 
        shift
        JSON_CONFIG=$1
    elif [ $p = "-sram" ]; then 
        APP_TYPE=BOOT_SRAM
        USE_SDRAM=true # Because SRAM is used, SDRAM is necessary for data 
    elif [ $p = "-qspi" ]; then 
        APP_TYPE=BOOT_QSPI
    elif [ $p = "-nvoices" ]; then
        shift
        NVOICES=$1
        MIDI=true
    elif [ $p = "-poly-mode" ]; then 
        shift 
        if [ "$1" == "blocking" ]; then 
            POLY_MODE="VOICE_BLOCKING"
        fi
    # -midi
    elif [ $p = "-midi" ]; then
        MIDI=true
    elif [ $p = "-midi-uart" ]; then 
        MIDI=true
        UART=true
    elif [ $p = "-rx-pin" ]; then 
        shift 
        RX_PIN=$1
    elif [ $p = "-tx-pin" ]; then 
        shift 
        TX_PIN=$1
    elif [ $p = "-patch" ]; then
        JSON_CONFIG="$FAUSTARCH/daisy/patch.json"
        PATCH=true
        SEED=true
    elif [ $p = "-pod" ]; then
        JSON_CONFIG="$FAUSTARCH/daisy/pod.json"
        POD=true
        SEED=true
    elif [ $p = "-seed" ]; then 
        SEED=true
    elif [ $p = "-patchsm" ]; then
        PATCHSM=true
    # -upload-sd
    elif [ $p = "-upload-sd" ]; then
        UPLOAD_SD=true
    # -sd : load soundfiles from the SD card at runtime instead of inlining to QSPI
    elif [ $p = "-sd" ]; then
        SOUNDFILE_MODE=sd
    # -sd-debug : print loaded soundfile info over USB serial at startup
    elif [ $p = "-sd-debug" ]; then
        SOUNDFILE_MODE=sd
        SD_DEBUG=true
    # -sdram
    elif [ $p = "-sdram" ]; then
        USE_SDRAM=true
    elif [ $p = "-mem-thresh" ]; then 
        shift
        MEM_THRESH=$1 
    # -sr
    elif [ $p = "-sr" ]; then
        shift
        SR=$1
        case "$SR" in
            8000)
                SRSTR="SAI_8KHZ"
                ;;
            16000)
                SRSTR="SAI_16KHZ"
                ;;
            32000)
                SRSTR="SAI_32KHZ"
                ;;
            48000)
                SRSTR="SAI_48KHZ"
                ;;
            96000)
                SRSTR="SAI_96KHZ"
                ;;
            *)
                echo "Sample rate does not match Daisy availabale sample rates (8K, 16K, 32K, 48K, 96K), automatic fall back to 48000"
                SR=48000
                SRSTR="SAI_48KHZ"
                ;;
        esac

    # -bs
    elif [ $p = "-bs" ]; then
        shift
        BS=$1
    # -source
    elif [  $p = "-source" ]; then
        SOURCE=true
    elif [[ -f "$p" ]]; then
        FILE="$p"
    # other compile options
    else
        OPTIONS="$OPTIONS $p"
    fi

shift
done


###########################
# Compile the *.dsp files
###########################

echo "About to compile DSP"
echo "$FILE"
for p in $FILE; do
    CUR=$(pwd)
    f=$(basename "$p")
    SRCDIR=$(dirname "$p")

    # creates the dir
    dspName="${f%.dsp}"
    rm -rf "$SRCDIR/$dspName"
    mkdir "$SRCDIR/$dspName"

    # SD card upload mode : flash a temporary uploader firmware, then stream the
    # WAV files referenced by the DSP into the /soundfiles folder on the card.
    # Does not build/flash the DSP itself.
    if [ $UPLOAD_SD == true ]; then
        echo "SD upload mode : building soundfile uploader firmware"
        faust "$p" -json -o "$SRCDIR/$dspName/ex_faust.cpp" || exit
        UPDIR="$SRCDIR/$dspName/_sd_uploader"
        mkdir -p "$UPDIR"
        cp "$FAUSTARCH/daisy/sd_uploader.cpp" "$UPDIR/"
        cp "$FAUSTARCH/daisy/Makefile.sd_uploader" "$UPDIR/Makefile"
        (cd "$UPDIR" && make) || exit
        # Flash the uploader, retrying until DFU succeeds. Proceeding with a
        # stale uploader still on the board (DFU not entered) is the common trap,
        # so do not continue to the upload until this actually flashes.
        echo "To flash the uploader, put the Daisy in DFU mode: hold BOOT, tap RESET, release BOOT."
        while true; do
            read -p "Press ENTER when the Daisy is in DFU mode (or type 's' to skip flashing): " resp
            if [ "$resp" = "s" ]; then
                echo "Skipping flash; using whatever firmware is already on the board."
                break
            fi
            if (cd "$UPDIR" && make program-dfu); then
                echo "Uploader flashed."
                break
            fi
            echo "Flash failed (Daisy not in DFU mode?). Try again."
        done
        echo "Waiting for Daisy to re-enumerate as USB serial..."
        sleep 3
        python3 "$FAUSTARCH/daisy/sd_upload.py" "$SRCDIR/$dspName.dsp.json" "$SRCDIR" || exit
        echo "Soundfiles uploaded to /soundfiles. Flash your DSP separately."
        continue
    fi

    GENERATED_ARCH="$SRCDIR/$dspName/daisy_arch.cpp"
    
    MEM_THRESH_UNIT=$(($MEM_THRESH/4))

    if [ "$JSON_CONFIG" = "0" ]; then 
        #eval "$(python3 "$FAUSTARCH/daisy/generate_config.py" "$SRCDIR/$dspName" $PATCHSM $UART $RX_PIN $TX_PIN)"
        out="$(python3 "$FAUSTARCH/daisy/generate_config.py" "$SRCDIR/$dspName" $PATCHSM $UART $RX_PIN $TX_PIN)" || {
            echo "generate_config.py failed" >&2; exit 1; }
        eval "$out"
    fi

    echo "Compile Faust to C++":

    cp -r "$FAUSTARCH/daisy/Makefile" "$SRCDIR/$dspName/"
    if [ $USE_SDRAM == true ]; then 
        # We have to compile it twice to provide json to python, before inlining generated code 
        faust -i -a "$ARCHFILE" $OPTIONS "$f" -json -mem1 -it -fpga-mem-th $MEM_THRESH_UNIT -o "$SRCDIR/$dspName/ex_faust.cpp" || exit
        out="$(python3 "$FAUSTARCH/daisy/faust_daisy_parser.py" "$SRCDIR/$dspName" $MEM_THRESH $NVOICES 1 $ARCHFILE $JSON_CONFIG $SOUNDFILE_MODE)" || {
            echo "faust_daisy_parser.py failed" >&2; exit 1; }
        eval "$out"
        faust -A "$SRCDIR/$dspName" -i -a "$GENERATED_ARCH" $OPTIONS "$f" -mem1 -it -fpga-mem-th $MEM_THRESH_UNIT -o "$SRCDIR/$dspName/ex_faust.cpp" || exit
    else
        # We have to compile it twice to provide json to python, before inlining generated code
        faust -i  -a "$ARCHFILE" $OPTIONS "$f" -json  -o "$SRCDIR/$dspName/ex_faust.cpp" || exit
        out="$(python3 "$FAUSTARCH/daisy/faust_daisy_parser.py" "$SRCDIR/$dspName" $MEM_THRESH $NVOICES 0 $ARCHFILE $JSON_CONFIG $SOUNDFILE_MODE)" || {
            echo "faust_daisy_parser.py failed" >&2; exit 1; }
        eval "$out"
        faust -A "$SRCDIR/$dspName" -i  -a "$GENERATED_ARCH" $OPTIONS "$f" -o "$SRCDIR/$dspName/ex_faust.cpp" || exit
    fi

    # for PATCH support
    if [ $PATCH == true ]; then
        echo "#define PATCH" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
    fi
    # for POD support
    if [ $POD == true ]; then
        echo "#define POD" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
    fi
    # for SEED SUPPORT
    if [ $SEED == true ]; then
        echo "#define SEED" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
    fi
    # for PODSM support
    if [ $PATCHSM == true ]; then
        echo "#define PATCHSM" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
    fi

    # for POLY
    if [ "$NVOICES" -gt 0 ]; then
        MIDI=true
        echo "Polyphonic DSP : setting nvoices $NVOICES"
        echo "#define $POLY_MODE" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
        echo "#define POLY" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
        echo "#define NVOICES $NVOICES" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
    fi

    if [ $UART == true ]; then 
        echo "#define MIDI_UART" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
    fi

    # for MIDI
    if [ $MIDI == true ]; then
        echo "#define MIDICTRL" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
    fi
    
    if [ $USE_SDRAM == true ]; then
        echo "#define USE_SDRAM" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
    fi

    # for Soundfile (set by faust_daisy_parser.py when the DSP uses soundfiles)
    if [ $USE_SOUNDFILE == true ]; then
        echo "#define USE_SOUNDFILE" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
    fi

    # Serial (UART) control link (set by faust_daisy_parser.py via SERIAL=true /
    # SERIAL_RX_INPUTS=N when the DSP uses [rx:Dx] / [tx:Dx] metadata).
    if [ $SERIAL == true ]; then
        echo "#define SERIAL" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
        echo "#define SERIAL_RX_INPUTS $SERIAL_RX_INPUTS" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
        echo "#define SERIAL_TX_OUTPUTS $SERIAL_TX_OUTPUTS" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
    fi

    # Hardware PWM (set by faust_daisy_parser.py via PWM=true / PWM_OUTPUTS=N
    # when the DSP uses [pwm:PIN] metadata).
    if [ $PWM == true ]; then
        echo "#define PWM" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
        echo "#define PWM_OUTPUTS $PWM_OUTPUTS" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
    fi

    # for SD-card soundfiles (set by faust_daisy_parser.py in -sd mode)
    if [ $USE_SD_SOUNDFILE == true ]; then
        echo "#define USE_SD_SOUNDFILE" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
        # Size the SDRAM arena to the referenced WAVs when the parser could
        # measure them (otherwise the architecture default of 32 MB is used).
        if [ -n "$SD_SOUNDFILE_BYTES" ]; then
            echo "#define FAUST_SD_SOUNDFILE_BYTES $SD_SOUNDFILE_BYTES" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
        fi
        # -sd-debug : enable the USB-serial dump of the loaded soundfile.
        if [ $SD_DEBUG == true ]; then
            echo "#define SD_SOUNDFILE_DEBUG" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
        fi
    fi

    # for Sample Rate
    echo "#define DAISY_SAMPLE_RATE daisy::SaiHandle::Config::SampleRate::$SRSTR" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"
    echo "#define MY_SAMPLE_RATE $SR" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"

    # for Buffer Size
    echo "#define MY_BUFFER_SIZE $BS" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"

    # for DAISY_NO_RTTI preprocessor flag (to avoid dynamic casts since libdaisy is compiled with -fno-rtti flag)
    echo "#define DAISY_NO_RTTI" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"


    echo "#include \"per/sai.h\"" | cat - "$SRCDIR/$dspName/ex_faust.cpp" > temp && mv temp "$SRCDIR/$dspName/ex_faust.cpp"

    # Soundfiles inline their samples as QSPI-resident const data, which only
    # fits when the program runs from QSPI. Warn if -qspi was not requested
    # (small soundfiles may still fit in internal flash; large ones will
    # overflow the FLASH region at link time).
    if [ $USE_SOUNDFILE == true ] && [ $APP_TYPE != BOOT_QSPI ]; then
        echo "WARNING: this DSP uses soundfiles. Audio data lives in QSPI flash;"
        echo "         build with -qspi unless the sound is small enough for the"
        echo "         128 KB internal flash."
    fi

    # SD-card soundfile mode needs the FatFS middleware linked into the DSP and
    # the runtime loader header copied next to the generated sources.
    SF_MAKE=""
    if [ $USE_SD_SOUNDFILE == true ]; then
        SF_MAKE="USE_FATFS=1"
        cp "$FAUSTARCH/daisy/daisy_sd_soundfile.hpp" "$SRCDIR/$dspName/"
        echo "SD soundfile mode : the DSP loads /soundfiles/*.wav at startup."
        echo "                    Upload them first with 'faust2daisy -upload-sd $f'."
    fi

    # Serial (UART) control link : copy the listener header next to the
    # generated sources (ex_faust.cpp #include "daisy_uart_listener.hpp").
    if [ $SERIAL == true ]; then
        cp "$FAUSTARCH/daisy/daisy_uart_listener.hpp" "$SRCDIR/$dspName/"
    fi

    # compile and install plugin or keep the source folder
    if [ $SOURCE == false ]; then
        export $APP_TYPE
        cd "$SRCDIR/$dspName"
        pwd
        echo "Building Faust program"
        (make APP_TYPE=$APP_TYPE $SF_MAKE) || exit

        if [ $APP_TYPE == BOOT_NONE ]; then
            read -p "Press ENTER when Daisy is in DFU mode"
            (make program-dfu $SF_MAKE)  || true
        else
            read -r -p "Do you want to flash Daisy bootloader ? [y/n] : " response
            case "$response" in
                [yY])
                    read -p "Press ENTER when Daisy is in DFU mode to flash bootloader"
                    (make program-boot) || true
                    sleep 1s # Make sure bootloader is operational
                ;;
                *)
            esac
            read -p "Press Daisy RESET button, then press ENTER (approximately 1 sec after RESET)"
            (make program-dfu APP_TYPE=$APP_TYPE $SF_MAKE)  || true
        fi

        cd ..
        #rm -rf "$SRCDIR/$dspName"
        echo "Success !"
    else
        echo "Create the $SRCDIR/$dspName folder"
    fi
done
