#!/bin/bash
#
# MakeSFZ-Yad_UI, create SFZ Libraries from Audio Samples.
# Concept and Testing by Glen MacArthur, coded by Google Gemini 3.
# Based on 'makesfz' by Dan MacDonald and Andrew M. Taylor.

# --- CUSTOM ASSETS ---
ICON_DIR="/usr/local/share/icons/custom"
APP_ICON="$ICON_DIR/makesfz.png"
IMG_ICON="$ICON_DIR/makesfz-app.png"

# --- CORE SFZ CONSTANTS ---
declare -ri KEY_MAX=127
declare -ri KEY_MIN=0
declare -ri VEL_MAX=127
declare -ri ERROR=-1

# --- STATE VARIABLES ---
declare -a instrumentNames
declare -ia instrNumLayers
declare -ia instrFilesIndex
declare -a sampleFilePaths
declare -a fileSearchPaths
declare -a dirSearchPaths
declare -i filesIndex=0

# --- HELPER: KEY VALIDATION ---
function checkKeyIsValid() {
    local input=$1
    local -i key=0
    if [[ $input =~ ^[a-gA-G](#|b)?(-1|[0-9])$ ]]; then
        case "${input:0:1}" in
            [cC] ) key=0;; [dD] ) key=2;; [eE] ) key=4;; [fF] ) key=5;;
            [gG] ) key=7;; [aA] ) key=9;; [bB] ) key=11;;
        esac
        [[ $input == *"#"* ]] && (( key++ ))
        [[ $input == *"b"* ]] && (( key-- ))
        [[ $input == *"-"* ]] && (( key -= 24 ))
        local octave=${input:(-1)}
        (( key += 12 * (octave + 1) ))
        [[ $key -lt $KEY_MIN || $key -gt $KEY_MAX ]] && key=$ERROR
    elif [[ $input =~ ^([0-9]|[1-9][0-9]|1[0-2][0-9])$ ]]; then
        key=$input
        [[ $key -gt $KEY_MAX ]] && key=$ERROR
    else
        key=$ERROR
    fi
    echo $key
}

# --- SFZ ENGINE FUNCTIONS ---

function getNumMatchingFiles() {
    find "$1" -maxdepth 1 -type f -iname "*.$fileType" -printf "%f/" | gawk 'BEGIN { RS = "/" } END { print NR }'
}

function getSortedFileNames() {
    local sortOrder=""
    [[ "$isReverseSortOn" == "true" ]] && sortOrder="r"

    # Default sorting for standard numeric filenames
    find "$1" -maxdepth 1 -type f -iname "*.$fileType" -printf "%f\0" | sort -z -n | tr '\0' '\n'
}

function findFilesWithDigits() {
    declare -iA instrLayerCounters
    local -i instrID=-1 

    # We use a process substitution to read sorted filenames
    while IFS= read -r fileName; do
        [[ -z "$fileName" ]] && continue
        
        local baseName="${fileName%.*}"
        # Aggressively strip trailing hyphen and digits (e.g., -1, -2)
        local groupName=$(echo "$baseName" | sed 's/-[0-9]\+$//')

        if [ "${instrLayerCounters[$groupName]}" == "" ]; then
            (( instrID++ ))
            instrumentNames[$instrID]="$groupName"
            instrFilesIndex[$instrID]=$filesIndex
            instrLayerCounters["$groupName"]=0
        fi

        (( instrLayerCounters["$groupName"]++ ))
        instrNumLayers[$instrID]=${instrLayerCounters["$groupName"]}
        sampleFilePaths[$filesIndex]="$fileName"
        (( filesIndex++ ))
        
    done < <(getSortedFileNames ".")
}

function findFilesSingleLayer() {
    while IFS= read -r fileName; do
        [[ -z "$fileName" ]] && continue
        instrumentNames+=("${fileName%.*}")
        instrNumLayers+=(1)
        sampleFilePaths+=("$fileName")
        instrFilesIndex+=($filesIndex)
        (( filesIndex++ ))
    done < <(getSortedFileNames ".")
}

function addGroup() {
    local -i numLayers=$1
    local -i fileID=$2
    local instrName=$3

    (( velIncrement = VEL_MAX / numLayers + 1 ))
    (( numBigIncrements = VEL_MAX % numLayers ))
    (( numLayers == VEL_MAX )) && velIncrement=1
    local -i lovel=1
    local -i hivel=$velIncrement
    local -i layerNum=1

    textLines+=("// $instrName")
    
    # SMART KEY DETECTION: If filename starts with "35-", use 35 as the key
    if [[ $instrName =~ ^([0-9]+) ]]; then
        key=${BASH_REMATCH[1]}
    fi

    textLines+=("<group> key=$key loop_mode=$loopMode lochan=$lochan hichan=$hichan")

    for (( l=0; l<numLayers; l++ )); do
        (( lovel > 127 )) && lovel=127
        (( hivel > 127 )) && hivel=127
        local pathClean="${sampleFilePaths[$fileID]//'/'/\\}"
        textLines+=("<region> lovel=$lovel hivel=$hivel sample=$pathClean")
        
        (( layerNum == numBigIncrements )) && (( velIncrement -= 1 ))
        (( lovel = hivel + 1 ))
        (( hivel += velIncrement ))
        (( layerNum++ ))
        (( fileID++ ))
    done
    textLines+=("")
}

function write_sfz() {
    local target="$1"
    [[ -f "$target.sfz" ]] && rm "$target.sfz"
    for line in "${textLines[@]}"; do
        printf '%s\n' "$line" >> "$target.sfz"
    done
}

# --- GUI LOGIC ---

function run_gui() {
    yad --title="SFZ Library Builder" \
        --window-icon="$APP_ICON" --image="$IMG_ICON" \
        --text="<b>SFZ Asset Mapping Tool</b>\n<i>Configure how your audio files are grouped into instruments.</i>\n*Suggested sample naming:\n MIDI Key#-Instrument Name-Layer# (ie 35-Kick-01)" \
        --separator="|" --form --align=right --width=600 --height=600 \
        --field="<b>Mode Selection</b>:LBL" "" \
        --field="Mapping Mode:CB" "Single SFZ File!Individual SFZ per File" \
        --field="Output Name (Single Mode):ENTRY" "${PWD##*/}" \
        --field="<b>Logic Settings</b>:LBL" "" \
        --field="Start Key (ie C4 or 60):ENTRY" "C4" \
        --field="Layering Logic:CB" "none!dirs!prefix!suffix" \
        --field="Sort Direction:CB" "Standard!Reverse" \
        --field="Loop Mode:CB" "no_loop!one_shot" \
        --field="MIDI Channel:CB" "all!1!2!3!4!5!6!7!8!9!10!11!12!13!14!15!16" \
        --field="":LBL "" \
        --field="<b>Layering Mode Reference:</b>:LBL" "" \
        --field="":TXT "• none: Every file becomes its own individual SFZ instrument.\n• dirs: Uses sub-folder names to group samples into velocity layers.\n• prefix/suffix: Groups by digits (e.g., 35-Kick-1, 35-Kick-2)." \
        --button="Build Library:0" --button="Cancel:1"
}

# --- MAIN EXECUTION ---

if (( $# > 0 )); then
    # If the first item is a directory, jump into it
    if [[ -d "$1" ]]; then
        cd "$1"
    else
        # If it's a file, jump to the folder containing that file
        cd "$(dirname "$1")"
    fi
    # Use the current directory for the rest of the logic
    dirSearchPaths=(".")
else
    dirSearchPaths=(".")
fi

GUI_OUT=$(run_gui)
[[ $? -ne 0 ]] && exit 0

IFS='|' read -r _L1 MODE OUT_NAME _L2 START_KEY L_MARKER SORT_DIR L_MODE M_CHAN _L3 _L4 _L5 _L6 <<< "$GUI_OUT"

[[ "$MODE" == "Single SFZ File" ]] && isSingleFile=true || isSingleFile=false
sfzFileName="$OUT_NAME"
loopMode="$L_MODE"
layerMarker="$L_MARKER"
[[ "$SORT_DIR" == "Reverse" ]] && isReverseSortOn=true || isReverseSortOn=false
fileType="wav" 

if [[ "$M_CHAN" == "all" ]]; then
    lochan=1; hichan=16
else
    lochan=$M_CHAN; hichan=$M_CHAN
fi

key=$(checkKeyIsValid "$START_KEY")
[[ $key -eq $ERROR ]] && key=60 # Fallback to Middle C

case $layerMarker in
    prefix|suffix ) findFilesWithDigits ;;
    * )             findFilesSingleLayer ;;
esac

[[ ${#sampleFilePaths[@]} -eq 0 ]] && { yad --error --text="No $fileType files found!"; exit 1; }

declare -a textLines
textLines+=("// Auto-generated by makesfz_yad_ui")
textLines+=("")

for (( i=0; i<${#instrumentNames[@]}; i++ )); do
    iName="${instrumentNames[$i]}"
    iLayers=${instrNumLayers[$i]}
    iIdx=${instrFilesIndex[$i]}
    
    if $isSingleFile; then
        addGroup "$iLayers" "$iIdx" "$iName"
    else
        textLines=()
        textLines+=("// Auto-generated by makesfz_yad_ui")
        addGroup "$iLayers" "$iIdx" "$iName"
        write_sfz "$iName"
    fi
done

if $isSingleFile; then
    write_sfz "$sfzFileName"
fi

yad --info --title="SFZ Library Builder" --text="SFZ Generation Complete!" \
 --center --width=400 --timeout=2 --window-icon="$APP_ICON"
exit 0