'------------------------------------------------------------------------------------------------------------
' SOUNDRA STEREO :: ROUTINES.bas
'------------------------------------------------------------------------------------------------------------
' SYSTEM
DEC FCT CheckArg              (AS TXT)                                     AS I32
DEC FCT CheckDir              (AS TXT, AS TXT)                             AS I32
DEC FCT FindPath              (AS TXT, AS TXT, AS TXT)                     AS TXT
DEC FCT SaveSettings          ()                                           AS I32
DEC FCT LoadSettings          (AS TXT="")                                  AS I32
DEC SUB LoadSettingsProcess   (AS TXT, AS I32)
DEC FCT CreateFileCopy        (AS TXT, AS TXT)                             AS TXT
DEC SUB BundleCopy            (AS I32)
' INTERFACE SYSTEM
DEC FCT SOUNDRA_ACTIONS       (AS THREAD_MESSAGE)                          AS I32
DEC SUB CALC_ZMAX             (AS I32)
DEC FCT SET_GUI_SIZE          ()                                           AS I32
DEC FCT DB_Ask                (AS I32, AS TXT, AS IOS, AS TXT="", AS I32, AS I32, AS I32) AS I32
DEC FCT DB_Answer             (AS I32, AS IOS)                             AS I32
DEC FCT DB_Cancel             ()                                           AS I32
DEC FCT MessageConsole        (BYREF AS TXT, AS I32)                       AS I32
DEC SUB ShowMessages          (AS I32)
DEC SUB MessageBar            (BYREF AS TXT)
DEC FCT BATCH_CONFIG_CONTROLS (AS TXT, AS TXT) AS I32
DEC SUB SET_CONNECTORS()
DEC FCT CreateInterface       (AS F32=1)                                   AS I32
DEC FCT OpenBrowser           (AS TXT, AS TXT="")                          AS I32
DEC FCT SelectFile            (AS TXT)                                     AS I32
DEC FCT FindActiveList        ()                                           AS I32
DEC SUB UpdateTuningControls  ()
DEC SUB FlushActionLog        ()
DEC FCT LogAction             (AS I32, AS SOUNDRA.MODULE PTR,AS I32=-1,AS SOUNDRA.MODULE PTR=0) AS I32
' WAVE FILES
DEC FCT SaveWF                (AS _WF_CATALOG PTR)                         AS I32
DEC FCT MakeOfflineWF         (BYREF AS _WF_CATALOG PTR PTR, AS _LIST_INFO,FILEPATH AS TXT, _
                               FILENAME AS TXT)                            AS I32
DEC FCT UndoLoadWF            (BYREF AS _WF_CATALOG PTR, AS I32)           AS I32
DEC FCT LoadSample   OVERLOAD (AS TXT, AS TXT)                             AS I32
DEC FCT LoadSample            (AS _WF_CATALOG PTR, AS I32)                 AS I32
DEC SUB DeleteSample          (AS I32)
DEC FCT LoadFilter            (AS TXT, AS TXT)                             AS I32
DEC FCT SaveFilter            (AS _WF_CATALOG PTR)                         AS I32
DEC FCT SaveWFLibrary         ()                                           AS I32
DEC FCT LoadWFLibrary         ()                                           AS I32
DEC FCT SaveMelody            (AS _WF_CATALOG PTR) AS I32
DEC FCT LoadMelody            (AS TXT, AS TXT)                             AS I32
DEC FCT FIND_IN_WF_LIB        (AS TXT, AS TXT, AS IOS)                     AS I32
'MODULE-RELATED
DEC SUB SET_MOD_SAMPLE        (AS SOUNDRA.MODULE PTR, AS _WF_CATALOG PTR, AS IOS=FALSE)
DEC FCT SetPortList           ()                                           AS I32
DEC FCT CreateModControls     (AS SOUNDRA.MODULE PTR,AS I32,AS I32)        AS I32
DEC FCT CreateModule          (AS I32, AS I32, AS I32, AS IOS=TRUE)        AS I32
DEC FCT DeleteModule          (AS SOUNDRA.MODULE PTR)                      AS I32
DEC SUB DISABLE_PARAM_FA      ()
DEC SUB ENABLE_PARAM_FA       (AS FRACTIONAID.DATA PTR)
DEC SUB SETUP_PARAM_FA        (AS SOUNDRA.MODULE PTR, AS I32)
DEC SUB PARAM_LIST_UPDATE     (AS I32)
DEC SUB SelectModule          (AS SOUNDRA.MODULE PTR, AS I32 = -1)
DEC FCT LoadModules           (AS SOUNDRA.MODULE_XP PTR PTR, AS I32)       AS IOS
DEC FCT SaveSession           (AS TXT, AS TXT)                             AS I32
DEC FCT LoadSession           (AS TXT, AS TXT)                             AS I32
DEC SUB SetMIDISelection      (AS SOUNDRA.MODULE PTR, AS I32)
DEC SUB VIEW_MOD              (AS I32)
'MIDI
DEC SUB SET_PARAM_FROM_KEY    (AS I32, AS I32=0)
DEC SUB KB_MIDI_MANAGER       (AS MIDI_CONTROL_MSG PTR)
DEC SUB REFRESH_MIDI_LISTS    (AS IOS=FALSE)
DEC SUB MIDIOPEN              (AS DWORD)
DEC SUB MIDICLOSE             (AS DWORD)
DEC SUB MIDI_INPUT            (AS HMIDIIN,AS UINT,AS DWORD,AS DWORD,AS DWORD)
'CONTROL FUNCTIONS FOR WHICH THE POINTER IS NEEDED
DEC FCT ctrl_sample_record    (AS I32,AS I32,AS I32,AS I32,AS I32=0)       AS I32
DEC FCT ctrl_melody_record    (AS I32,AS I32,AS I32,AS I32,AS I32=0)       AS I32
DEC FCT ctrl_melgen_record    (AS I32,AS I32,AS I32,AS I32,AS I32=0)       AS I32
DEC FCT ctrl_kb               (AS I32,AS I32,AS I32,AS I32,AS I32=0)       AS I32
'FRACTION-AID
DEC FCT SET_TEMPO             (BYREF AS F32) AS IOS
DEC FCT SET_RF                (BYREF AS F32) AS IOS    'REFERENCE FREQUENCY (TUNING)
DEC FCT SET_AK                (BYREF AS F32) AS IOS    'ABSOLUTE KEY (TUNING)
DEC FCT SET_RK                (BYREF AS F32) AS IOS    'RELATIVE KEY (TUNING)
'------------------------------------------------------------------------------------------------------------
'                             SYSTEM ROUTINES
'------------------------------------------------------------------------------------------------------------
FUNCTION CheckArg(Arg AS TXT)                                                             AS I32
     DIM AS TXT ARG2 = TRIM(ARG)
     SELECT CASE ARG2
     CASE ""
          RETURN 0
     CASE ELSE 'FILE?
          DIM AS TXT EXT, FILE_PATH, FILE_NAME, FILE_NICK
          DIM AS I32 I,J
          IF NOT(FILEEXISTS(ARG2))    THEN RETURN 0
          'EXTENSION
          I = INSTRREV(ARG2,".")
          IF I = 0 THEN RETURN 0
          EXT = MID(ARG2,I)
          IF UCASE(EXT) <> ".SOUNDRA" THEN RETURN 0
          'PATH
          J = INSTRREV(ARG2,ANY "\/")
          IF J = 0 THEN RETURN 0
          FILE_PATH = MID(ARG2,1,J-1)
          FILE_NAME = MID(ARG2,J+1)
          I -= J
          FILE_NICK = MID(FILE_NAME,1,I-1)
          'SET TO BE OPEN
          SETTINGS.SESSION_PATH = FILE_PATH
          SETTINGS.SESSION_FILE = FILE_NAME
          SETTINGS.SESSION_NAME = FILE_NICK
     END SELECT
     RETURN 0
END FUNCTION

FUNCTION CheckDir(DIR_NAME AS TXT, DIRPATH AS TXT)                                        AS I32
     DIM AS I32 R
     IF CHDIR(DIRPATH & "\" & DIR_NAME) THEN
          R = MKDIR(DIRPATH & "\" & DIR_NAME)
     ELSE
          R = CHDIR("..")
     END IF
     IF R THEN TERMINAL(ERR2TXT(R),MSG_FATAL_ERROR)
     RETURN R
END FUNCTION

FUNCTION FindPath(FILE_PATH AS TXT, FILE_NAME AS TXT, TARGET_PATH AS TXT) AS TXT
     DIM AS TXT NEW_PATH,TMPSTR
     DIM AS I32 P

     IF FILEEXISTS(FILE_PATH & "\" & FILE_NAME) THEN RETURN FILE_PATH

     P = INSTRREV(FILE_PATH,TARGET_PATH)
     IF P > 0 THEN
          NEW_PATH = HOMEPATH & MID(FILE_PATH,P)
          IF FILEEXISTS(NEW_PATH & "\" & FILE_NAME) THEN RETURN NEW_PATH
     END IF

     NEW_PATH = HOMEPATH & TARGET_PATH
     IF FILEEXISTS(NEW_PATH & "\" & FILE_NAME)  THEN RETURN NEW_PATH
     '¤¤¤#145: "Can't find "
     TMPSTR = LNG_SET_EXT(145,"Can't find ")
     TMPSTR &= FILE_NAME
     TERMINAL(TMPSTR,MSG_WARNING)
     RETURN ""
END FUNCTION

FUNCTION SaveSettings()                                                                   AS I32
     DIM AS I32  r, ff
     MUTEXLOCK   MT_FF
     ff = FREEFILE
     r  = OPEN(HOMEPATH & "/Settings.txt", FOR OUTPUT, AS #ff)
     MUTEXUNLOCK MT_FF
     IF r = 0 THEN
          WRITE #ff, "DISPLAY DRIVER",DISPLAY_DRIVER_NAME(SETTINGS.VIDEO_DRIVER_INDEX)
          WRITE #ff, "SCREEN WIDTH",SETTINGS.SCREEN_WIDTH
          WRITE #ff, "SCREEN HEIGHT",SETTINGS.SCREEN_HEIGHT
          WRITE #ff, "MONITORS",SETTINGS.MONITORS
          WRITE #FF, "ZOOM FACTOR",SETTINGS.ZOOM_FACTOR
          WRITE #FF, "USE SHADER",LOGIC2TEXT(SETTINGS.USE_SHADER,"TRUE","FALSE")
          WRITE #ff, "CPU-SAVING",LOGIC2TEXT(SETTINGS.FORCE_CTHROTTLE,"TRUE","FALSE")
          WRITE #ff, "LANGUAGE",LANG_ABRV(SETTINGS.LANGUAGE)
          
          WRITE #ff, "OUTPUT DEVICE",SETTINGS.OUTPUT_DEVICE_NAME
          WRITE #ff, "INPUT DEVICE",SETTINGS.INPUT_DEVICE_NAME
          WRITE #ff, "OUTPUT BUFFER LENGTH",SETTINGS.OUTPUT_BUFFER_LENGTH
          WRITE #ff, "AUTO-CONNECT",LOGIC2TEXT(SETTINGS.AUTO_CONNECT,"TRUE","FALSE")
          WRITE #ff, "USE DEFAULT DEVICE",LOGIC2TEXT(SETTINGS.USE_DEFAULT,"TRUE","FALSE")
          
          SETTINGS.TIME_BASE = PRIME(SOUNDRA.TIME_BASE)
          WRITE #ff, "TIME BASE",SETTINGS.TIME_BASE
          WRITE #ff, "AUTO-SAVE",LOGIC2TEXT(SETTINGS.AUTOSAVE,"TRUE","FALSE")
          WRITE #ff, "DISPLAY MIDI INPUT",LOGIC2TEXT(SETTINGS.DISPLAY_MIDIIN,"TRUE","FALSE")
          WRITE #ff, "SCOPE",SETTINGS.SCOPE_MODE
          WRITE #ff, "SESSION",SETTINGS.SESSION_PATH,SETTINGS.SESSION_FILE
          CLOSE #ff
     ELSE
          RETURN -r
     END IF
     RETURN 0
END FUNCTION

FUNCTION LoadSettings(FULLFILENAME AS TXT="")                                             AS I32
     DIM AS I32     R,FF
     DIM AS TXT     TMP_STR
     IF FULLFILENAME = "" THEN FULLFILENAME = HOMEPATH & "\Settings.txt"
     IF NOT(FILEEXISTS(FULLFILENAME)) THEN RETURN -_FNF
     MUTEXLOCK   MT_FF
     FF = FREEFILE
     R  = OPEN(FULLFILENAME, FOR INPUT, AS #FF)
     MUTEXUNLOCK MT_FF
     IF R THEN RETURN -R
     DO UNTIL EOF(FF)
          INPUT #FF, TMP_STR
          LoadSettingsProcess(TMP_STR,FF)
     LOOP
     CLOSE #FF
     RETURN 0
END FUNCTION

SUB LoadSettingsProcess(SettingName AS TXT, FF AS I32)
     DIM AS TXT SettingName2 = UCASE(TRIM(SettingName))
     IF NOT(SettingName2="") THEN
          DIM AS I32     I,J
          DIM AS TXT     TMPSTR
          'REMOVE ':' (RETROCOMPAT)
          J = INSTRREV(SettingName2,":")
          IF J > 0 THEN SettingName2 = MID(SettingName2,1,J-1) & MID(SettingName2,J+1)
          SELECT CASE SettingName2
          'CHECK VALUE
          CASE "DISPLAY DRIVER" 'DEFAULT IS 0 = GDI
               INPUT #FF,TMPSTR
               TMPSTR = TRIM(TMPSTR)
               DisplayDriverNum = GDI
               FOR I = 0 TO 2
                    IF TMPSTR = DISPLAY_DRIVER_NAME(I) THEN
                         DisplayDriverNum            = I
                         SETTINGS.VIDEO_DRIVER_INDEX = DisplayDriverNum
                         EXIT FOR
                    END IF
               NEXT I
          CASE "SCREEN WIDTH"
               INPUT #FF,TMPSTR
               SETTINGS.SCREEN_WIDTH = VALINT(TMPSTR)
               FLOOR(SETTINGS.SCREEN_WIDTH,1280)
               CEIL(SETTINGS.SCREEN_WIDTH,4096)
          CASE "SCREEN HEIGHT"
               INPUT #FF,TMPSTR
               SETTINGS.SCREEN_HEIGHT = VALINT(TMPSTR)
               FLOOR(SETTINGS.SCREEN_HEIGHT,720)
               CEIL(SETTINGS.SCREEN_HEIGHT,2160)
          CASE "MONITORS"
               INPUT #FF,TMPSTR
               SETTINGS.MONITORS = VALINT(TMPSTR)
               FLOOR(SETTINGS.MONITORS,1)
               CEIL(SETTINGS.MONITORS,NB_MONITORS)
               CEIL(SETTINGS.MONITORS,3)
               SETTINGS.MONITORS_CURRENT = SETTINGS.MONITORS
          CASE "ZOOM FACTOR"
               INPUT #FF,TMPSTR
               TMPSTR = TRIM(TMPSTR)
               SETTINGS.ZOOM_FACTOR = CSNG(TMPSTR)
               SETTINGS.ZOOM_FACTOR = INT(5*SETTINGS.ZOOM_FACTOR)/5
               FLOOR(SETTINGS.ZOOM_FACTOR,SETTINGS.ZMIN)
               SETTINGS.ZOOM_FACTOR_CURRENT = SETTINGS.ZOOM_FACTOR
          CASE "USE SHADER"
               INPUT #FF,TMPSTR
               TMPSTR = TRIM(TMPSTR)
               SETTINGS.USE_SHADER = TEXT2LOGIC(TMPSTR)
               RECTIFY(SETTINGS.USE_SHADER)
          CASE "OUTPUT DEVICE",TMPSTR
               INPUT #FF,TMPSTR
               SETTINGS.OUTPUT_DEVICE_NAME = TRIM(TMPSTR)
          CASE "INPUT DEVICE"
               INPUT #FF,TMPSTR
               SETTINGS.INPUT_DEVICE_NAME = TRIM(TMPSTR)
          CASE "OUTPUT BUFFER LENGTH"
               INPUT #FF,TMPSTR
               SETTINGS.OUTPUT_BUFFER_LENGTH  = VALINT(TMPSTR) - 1
               FLOOR(SETTINGS.OUTPUT_BUFFER_LENGTH,1)
               CEIL(SETTINGS.OUTPUT_BUFFER_LENGTH,32)
          CASE "AUTO-CONNECT"
               INPUT #FF,TMPSTR
               TMPSTR = TRIM(TMPSTR)
               SETTINGS.AUTO_CONNECT = TEXT2LOGIC(TMPSTR)
               RECTIFY(SETTINGS.AUTO_CONNECT)
          CASE "USE DEFAULT DEVICE"
               INPUT #FF,TMPSTR
               TMPSTR = TRIM(TMPSTR)
               SETTINGS.USE_DEFAULT = TEXT2LOGIC(TMPSTR)
               RECTIFY(SETTINGS.USE_DEFAULT)
          CASE "SESSION","LAST SYNTH"
               DIM AS I32 J
               INPUT #FF,SETTINGS.SESSION_PATH
               INPUT #FF,SETTINGS.SESSION_FILE
               SETTINGS.SESSION_PATH = TRIM(SETTINGS.SESSION_PATH)
               SETTINGS.SESSION_FILE = TRIM(SETTINGS.SESSION_FILE)
               J = INSTRREV(SETTINGS.SESSION_FILE,".")
               '¤¤¤#146: "[NEW SESSION]"
               TMPSTR = LNG_SET_EXT(146,"[NEW SESSION]")
               SETTINGS.SESSION_NAME = TMPSTR
               IF J THEN SETTINGS.SESSION_NAME = MID(SETTINGS.SESSION_FILE,1,J-1)
          CASE "AUTO-SAVE","AUTO SAVE"
               INPUT #FF,TMPSTR
               TMPSTR = TRIM(TMPSTR)
               SETTINGS.AUTOSAVE = TEXT2LOGIC(TMPSTR)
               RECTIFY(SETTINGS.AUTOSAVE)
          CASE "CPU-SAVING","CPU SAVING"
               INPUT #FF,TMPSTR
               TMPSTR = TRIM(TMPSTR)
               SETTINGS.FORCE_CTHROTTLE = TEXT2LOGIC(TMPSTR)
               RECTIFY(SETTINGS.FORCE_CTHROTTLE)
          CASE "TIME BASE"
               DIM AS I32 T
               INPUT #FF,TMPSTR
               TMPSTR = TRIM(TMPSTR)
               T = VAL(TMPSTR)
               IF     T = 5 THEN
                    SOUNDRA.TIME_BASE = 2
               ELSEIF T = 3 THEN
                    SOUNDRA.TIME_BASE = 1
               ELSE
                    SOUNDRA.TIME_BASE = 0
               END IF
               SETTINGS.TIME_BASE = PRIME(SOUNDRA.TIME_BASE)
          CASE "LANGUAGE"
               INPUT #FF,TMPSTR
               TMPSTR = UCASE(TRIM(TMPSTR))
               FOR I = 0 TO NB_LANGUAGES-1
                    IF TMPSTR = LANG_ABRV(I) THEN 
                         SETTINGS.LANGUAGE = I
                         EXIT FOR
                    END IF
               NEXT I
               'LANGUAGE = SETTINGS.LANGUAGE
          CASE "DISPLAY MIDI INPUT"
               INPUT #FF,TMPSTR
               TMPSTR = TRIM(TMPSTR)
               SETTINGS.DISPLAY_MIDIIN = TEXT2LOGIC(TMPSTR)
               RECTIFY(SETTINGS.DISPLAY_MIDIIN)
          CASE "SCOPE"
               INPUT #FF,TMPSTR
               TMPSTR = TRIM(TMPSTR)
               SETTINGS.SCOPE_MODE = VALINT(TMPSTR)
               FLOOR(SETTINGS.SCOPE_MODE,0)
               CEIL(SETTINGS.SCOPE_MODE,SOUNDRA.DRAW_MODES.NUMBER)
          CASE ELSE
               INPUT #FF,TMPSTR
          END SELECT
     END IF
END SUB 

SUB CALC_ZMAX(W AS I32)
     DIM AS I32 I
     I = 3
     WHILE W/I > 320
          I += 1
     WEND
     SETTINGS.ZMAX = (I-1)/BASE_GRID_STEP
     FLOOR(SETTINGS.ZMAX,SETTINGS.ZMIN)
     CEIL(SETTINGS.ZOOM_FACTOR,SETTINGS.ZMAX)
END SUB
FUNCTION SET_GUI_SIZE() AS I32
     DIM AS I32 TEXT_SIZE
     DIM AS TXT TMPSTR
     CALC_ZMAX(SETTINGS.SCREEN_WIDTH*SETTINGS.MONITORS)
     SaveSettings()
     ICON_SIZE      = 2*INT(BASE_ICON_SIZE * SETTINGS.ZOOM_FACTOR / 2)
     DEF_FONT_SIZE  = INT(BASE_FONT_SIZE   * SETTINGS.ZOOM_FACTOR)
     BORDER_SIZE    = INT(BASE_BORDER_SIZE * SETTINGS.ZOOM_FACTOR + .3)
     TEXT_SIZE      = DEF_FONT_SIZE
     TEXT_FONT      = MainFont
     GRID_STEP      = INT(SETTINGS.ZOOM_FACTOR * 5)    'PIXELS {3,4,5,6..}
     'SET ICON WIDTHS
     FOR I AS I32 = 1 TO 121
          TMPSTR = CHR(I)
          IconFontW[I] = GetFT_W(TMPSTR,IconFont,ICON_SIZE)
          IF IconFontW[I] = -1 THEN RETURN -_MAF
     NEXT I
     SETTINGS.ZOOM_FACTOR_CURRENT = SETTINGS.ZOOM_FACTOR
     SETTINGS.MONITORS_CURRENT    = SETTINGS.MONITORS
     RETURN 0
END FUNCTION

FUNCTION GetConsoleMsg(INDEX AS I32)                                                      AS TXT
     DIM AS TXT MSG
     MUTEXLOCK   LI_ConsoleMSG.MUTEX
          IF (INDEX > -1) AND (INDEX < LI_ConsoleMSG.LENGTH) THEN
               MSG        = ConsoleMSG[index]->msg
               FOREGROUND = msg_flag_color(ConsoleMSG[index]->msg_type)
          END IF
     MUTEXUNLOCK LI_ConsoleMSG.MUTEX
     RETURN MSG
END FUNCTION

FUNCTION MessageConsole(BYREF MSG AS TXT, FLAG AS I32) AS I32
     DIM AS I32     R
     DIM AS TXT     MSG2 = MSG

     'SHOW BAR?
     IF (InterfaceSet AND ScrSet AND VIDEO_THREAD_ACTIVE) = TRUE THEN MessageBar(MSG2)

     'LIST
     IF ConsoleMSG THEN
          MT_LIST_DOUBLING(ConsoleMSG,LI_ConsoleMSG,_CONSOLE_MSG)
          IF R = 0 THEN
               'APPEND MESSAGE
               MUTEXLOCK   LI_ConsoleMSG.MUTEX
               ConsoleMSG[LI_ConsoleMSG.LENGTH] = NEW _CONSOLE_MSG
               IF ConsoleMSG[LI_ConsoleMSG.LENGTH] THEN
                    ConsoleMSG[LI_ConsoleMSG.LENGTH]->msg      = MSG2
                    ConsoleMSG[LI_ConsoleMSG.LENGTH]->msg_type = FLAG
                    LI_ConsoleMSG.LENGTH += 1
               END IF
               MUTEXUNLOCK LI_ConsoleMSG.MUTEX
          END IF
     END IF

     'LOG
     MSG2 = msg_flag_text(FLAG) & " " & TIME & " : " & MSG2
     PRINT #LOG_FILE_NUMBER, MSG2

     'DISPLAY DIRECT ON SCREEN 
     IF (InterfaceSet = FALSE) OR (ScrSet = FALSE) OR (VIDEO_THREAD_ACTIVE = FALSE) THEN
          DIM AS UI32 OFFSET = IIF(ScrSet = FALSE, 5, 0)
          COLOR msg_flag_color(flag+OFFSET),0
          PRINT SetText(MSG2)
     END IF

     RETURN R
END FUNCTION

SUB ShowMessages(MSG_TYPE AS I32)
     DIM AS THREAD_MESSAGE    TMP_MSG
     FLOOR(MSG_TYPE,0)
     CEIL(MSG_TYPE,NB_MSG_TYPES-1)
     TMP_MSG.CODE    = _REMOTE_CTRL
     TMP_MSG.DEST_ID = ControlAccess[CS_BROWSER_TAB0] + T_MESSAGES
     TMP_MSG.ACTION  = CA_MD
     SENDMTMSG(TMP_MSG,,FALSE)
     TMP_MSG.DEST_ID = ControlAccess[CS_CONSOLE_TAB0] + MSG_TYPE
     SENDMTMSG(TMP_MSG)
END SUB

SUB MessageBar(BYREF MSG AS TXT)
     Controls[ControlAccess[CS_MSG_BAR]]->text.content = MSG
     Controls[ControlAccess[CS_MSG_BAR]]->UPDATE_GTEXT()
     NEW_MSG_BAR = TRUE
     IF   Controls[ControlAccess[CS_DIALOG]]->VISIBLE    = FALSE THEN
          Controls[ControlAccess[CS_MSG_BAR]]->VISIBLE   = TRUE
          Controls[ControlAccess[CS_CTRL_INFO]]->VISIBLE = FALSE
     END IF
END SUB

FUNCTION CreateBlankWF(FILEPATH AS TXT, FILENAME AS TXT, DURATION AS F32, NBSC AS I32, _
SAMPLERATE AS I32=SOUNDRA.SR) AS I32
     DIM AS I32 R,ID,LEN_MONO,BUFFER_LENGTH
     DIM AS TXT TMPSTR
     IF DURATION <= 0 OR NBSC < 1 THEN RETURN -_IFP
     'MEMORY
     MT_LIST_DOUBLING(WF_Sample,LI_WF_Sample,_WF_CATALOG)
     IF R THEN RETURN R
     ID = LI_WF_Sample.LENGTH
     LEN_MONO      = DURATION * SAMPLERATE
     BUFFER_LENGTH = LEN_MONO * NBSC
     'HEADER
     WF_Sample[ID]                     = NEW _WF_CATALOG
     WF_Sample[ID]->ID                 = ID
     WF_Sample[ID]->CONTENT            = WFC_HEADER
     WF_Sample[ID]->STATUS             = WFS_RESTRICTED
     WF_Sample[ID]->ON_DISK            = FALSE
     WF_Sample[ID]->PATH               = FILEPATH
     WF_Sample[ID]->FILENAME           = FILENAME
     WF_Sample[ID]->ResamplingFactor.N = 1
     WF_Sample[ID]->ResamplingFactor.D = 1
     WF_Sample[ID]->Duration           = DURATION
     WF_Sample[ID]->F0(0)              = 4/LEN_MONO
     WF_Sample[ID]->F0(1)              = WF_Sample[ID]->F0(0)
     WF_Sample[ID]->WAVEFILE.FACT      = LEN_MONO
     WF_Sample[ID]->WAVEFILE.DATASIZE  = 4*BUFFER_LENGTH
     WF_Sample[ID]->WAVEFILE.SET_FORMAT(NBSC,SAMPLERATE)
     'DATA
     WF_Sample[ID]->WAVEFILE.AUDIODATA = NEW F32[BUFFER_LENGTH]
     IF WF_Sample[ID]->WAVEFILE.AUDIODATA = 0 THEN
          '¤¤¤#147: "Unable to create new wave file."
          TMPSTR = LNG_SET_EXT(147,"Unable to create new wave file.")
          TERMINAL(TMPSTR,MSG_ERROR)
          DELETE WF_Sample[ID]
          RETURN -_MAF
     END IF
     WF_Sample[ID]->CONTENT = WFC_FILE
     LI_WF_Sample.LENGTH   += 1
     RETURN 0
END FUNCTION

FUNCTION MakeOfflineWF(BYREF LIST AS _WF_CATALOG PTR PTR, LI AS _LIST_INFO, FILEPATH AS TXT, _
FILENAME AS TXT) AS I32
     DIM AS I32 ID, R
     MT_LIST_DOUBLING(LIST,LI,_WF_CATALOG)
     IF R THEN RETURN R
     ID = LI.LENGTH
     LIST[ID]           = NEW _WF_CATALOG
     LIST[ID]->FileName = FILENAME
     LIST[ID]->Path     = FILEPATH
     LIST[ID]->STATUS   = WFS_FREE
     LIST[ID]->CONTENT  = WFC_BLANK
     LIST[ID]->ID       = ID
     LIST[ID]->ON_DISK  = TRUE
     MUTEXLOCK   LI.MUTEX
     LI.LENGTH += 1
     MUTEXUNLOCK LI.MUTEX
     RETURN ID
END FUNCTION

FUNCTION UndoLoadWF(BYREF WF_PTR AS _WF_CATALOG PTR, R AS I32) AS I32
     IF WF_PTR > 0 THEN DELETE WF_PTR
     WF_PTR = 0
     RETURN R
END FUNCTION

'CHECK IF FILE IS ALREADY PRESENT IN LIBRARY
FUNCTION FIND_IN_WF_LIB(FILEPATH AS TXT, FILENAME AS TXT, STRICT AS IOS) AS I32
     DIM AS I32 ID = -1
     DIM AS I64 S0,S1
     OPEN FILEPATH & "\" & FILENAME FOR INPUT AS #10
     S0 = LOF(10)
     CLOSE #10
     FOR I AS I32 = 0 TO LI_WF_Library.LENGTH-1
          IF WF_Library[I]->FileName = FILENAME THEN
               IF WF_Library[I]->Path = FILEPATH ORELSE INSTRREV(WF_Library[I]->Path,"CONVERTED") THEN
                    ID = I
                    EXIT FOR
               ELSEIF STRICT = TRUE THEN 'COMPARE SIZES
                    OPEN WF_Library[I]->Path & "\" & WF_Library[I]->FileName FOR INPUT AS #10
                    S1 = LOF(10)
                    CLOSE #10
                    IF S0 = S1 THEN
                         ID = I
                         EXIT FOR
                    END IF
               END IF
          END IF
     NEXT I
     RETURN ID
END FUNCTION

FUNCTION LoadSample(FILEPATH AS TXT, FILENAME AS TXT) AS I32
     DIM AS I32  R,ID
     DIM AS TXT  FULL_FILE_NAME = FILEPATH & "\" & FILENAME
     DIM AS IOS  CONVERTED

     'CREATE NEW ENTRY
     MT_LIST_DOUBLING(WF_Library,LI_WF_Library,_WF_CATALOG)
     IF R THEN RETURN R
     ID = LI_WF_Library.LENGTH
     WF_Library[ID]           = NEW _WF_CATALOG
     WF_Library[ID]->FileName = FILENAME
     WF_Library[ID]->Path     = FILEPATH
     WF_Library[ID]->STATUS   = WFS_RESTRICTED

     'LOAD HEADER
     R = LoadWaveHdr(FULL_FILE_NAME, WF_Library[ID]->WaveFile)
     IF R THEN RETURN UndoLoadWF(WF_Library[ID],R)
     WF_Library[ID]->ID       = ID
     WF_Library[ID]->CONTENT  = WFC_HEADER

     'RESAMPLING FACTOR
     WF_Library[ID]->ResamplingFactor.N = SETTINGS.SAMPLE_RATE
     WF_Library[ID]->ResamplingFactor.D = WF_Library[ID]->WaveFile.WaveFormat.SampleRate
     WF_Library[ID]->ResamplingFactor.SIMPLIFY()

     'LOAD CONTENT
     IF WF_Library[ID]->WaveFile.WaveFormat.FormatTag = _WAVE_FORMAT_INT OR _
        WF_Library[ID]->WaveFile.WaveFormat.SampleRate <> SETTINGS.SAMPLE_RATE THEN
          WF_Library[ID]->STATUS = WFS_CONVERTING
          'LOAD FILE
          R = LoadWaveData(FULL_FILE_NAME, WF_Library[ID]->WaveFile)
          IF R THEN RETURN UndoLoadWF(WF_Library[ID],R)
          WF_Library[ID]->CONTENT = WFC_FILE
     END IF

     'CONVERT RESOLUTION
     IF WF_Library[ID]->WaveFile.WaveFormat.FormatTag = _WAVE_FORMAT_INT THEN
          R = ConvertBitRes(WF_Library[ID]->WaveFile)
          IF R THEN RETURN UndoLoadWF(WF_Library[ID],R)
          CONVERTED = TRUE
     END IF

     'CONVERT SAMPLING RATE
     IF WF_Library[ID]->WaveFile.WaveFormat.SampleRate <> SETTINGS.SAMPLE_RATE THEN
          'R = ConvertSampleRate(WF_Library[ID]->WaveFile)
          'IF R THEN RETURN UndoLoadWF(WF_Library[ID],R)
          'CONVERTED = TRUE
     END IF

     'DURATION
     WF_Library[ID]->Duration = WF_Library[ID]->WaveFile.Fact/WF_Library[ID]->WaveFile.WaveFormat.SampleRate
     'F0
     WF_Library[ID]->F0(0) = 4 / WF_Library[ID]->WaveFile.Fact
          '!WILL BE REMOVED ONCE CONVERSION IS SUPPORTED
     IF (WF_Library[ID]->ResamplingFactor > 0) AND (WF_Library[ID]->ResamplingFactor <> 1) THEN _
        WF_Library[ID]->F0(0) /= WF_Library[ID]->ResamplingFactor
     WF_Library[ID]->F0(1) = WF_Library[ID]->F0(0)
     
     'SAVE CONVERTED
     IF CONVERTED AND WF_Library[ID]->CONTENT=WFC_FILE AND WF_Library[ID]->WaveFile.AudioData > 0 THEN
          DIM AS TXT TMPSTR
          TMPSTR = HOMEPATH & "\AUDIO\CONVERTED\" & FILENAME
          'SAVE TO CONVERTED FOLDER
          R = SaveWave(TMPSTR, WF_Library[ID]->WaveFile,32)
          IF R THEN 
               TERMINAL("Saving converted audio: " & ERR2TXT(R),MSG_ERROR)
          ELSE
               WF_Library[ID]->PATH = HOMEPATH & "\AUDIO\CONVERTED"
          END IF
     END IF

     'UNLOAD IF ANY
     IF WF_Library[ID]->WaveFile.AudioData THEN
          DELETE[] WF_Library[ID]->WaveFile.AudioData
          WF_Library[ID]->WaveFile.AudioData = 0
     END IF
     WF_Library[ID]->CONTENT = WFC_HEADER
     WF_Library[ID]->STATUS  = WFS_FREE
     WF_Library[ID]->ON_DISK = TRUE

     MUTEXLOCK   LI_WF_Library.MUTEX
     LI_WF_Library.LENGTH   += 1
     MUTEXUNLOCK LI_WF_Library.MUTEX
     WF_LIBRARY_UPDATED      = TRUE
     
     RETURN ID
END FUNCTION 

FUNCTION LoadSample(WF AS _WF_CATALOG PTR, MODE_INDEX AS I32) AS I32
     DIM AS I32               R, ID
     DIM AS TXT               FULL_FILE_NAME
     IF WF->STATUS > WFS_FREE THEN RETURN -1
     'SAMPLE FILE
     MT_LIST_DOUBLING(WF_Sample,LI_WF_Sample,_WF_CATALOG)
     IF R THEN RETURN R
     ID = LI_WF_Sample.LENGTH
     WF_Sample[ID]          = NEW _WF_CATALOG
     *WF_Sample[ID]         = *WF
     WF_Sample[ID]->ID      = ID
     WF_Sample[ID]->CONTENT = WFC_HEADER
     WF_Sample[ID]->STATUS  = WFS_RESTRICTED
     'AUDIO DATA
     FULL_FILE_NAME = WF_Sample[ID]->PATH & "\" & WF_Sample[ID]->FILENAME
     R = LoadWaveData(FULL_FILE_NAME, WF_Sample[ID]->WAVEFILE)
     IF R THEN RETURN UndoLoadWF(WF_Sample[ID], R)
     WF_Sample[ID]->CONTENT = WFC_FILE
     'PROCESS - MODE = {-1: ALL, 0: MONO, N}
     IF (WF_Sample[ID]->WAVEFILE.WAVEFORMAT.NBCHANNELS = 1) OR _
        (WF_Sample[ID]->WAVEFILE.WAVEFORMAT.NBCHANNELS+1 = MODE_INDEX) THEN 
          WF_Sample[ID]->MODE = -1
     ELSE
          WF_Sample[ID]->MODE = MODE_INDEX
     END IF
     IF WF_Sample[ID]->MODE > -1 THEN
          DIM AS I32 I,  NEW_LENGTH = WF_Sample[ID]->WAVEFILE.FACT
          DIM AS F32 PTR NEW_AUDIODATA
          'UPDATE META DATA
          WF_Sample[ID]->WAVEFILE.WAVEFORMAT.NBCHANNELS   = 1
          WF_Sample[ID]->WAVEFILE.WAVEFORMAT.BytesPerSec /= 2
          WF_Sample[ID]->WAVEFILE.WAVEFORMAT.FrameSize   /= 2
          WF_Sample[ID]->WAVEFILE.DATASIZE               /= 2
          'PROCESS DATA
          NEW_AUDIODATA = NEW F32[NEW_LENGTH]
          IF NEW_AUDIODATA = 0 THEN
               WF_Sample[ID]->MODE = -1
               TERMINAL(ERR2TXT(-_MAF),MSG_ERROR)
          ELSE
               IF WF_Sample[ID]->MODE = 0 THEN
                    DIM AS F32 X
                    'MERGE CHANNELS
                    FOR I = 0 TO WF_Sample[ID]->WAVEFILE.FACT - 1
                         X = WF_Sample[ID]->WAVEFILE.AUDIODATA[I+I] + WF_Sample[ID]->WAVEFILE.AUDIODATA[I+I]
                         NEW_AUDIODATA[I] = X/2
                    NEXT I
               ELSE
                    DIM AS I32 SCHAN = -1, SCHAN2
                    'REMOVE ONE CHANNEL
                    SCHAN  = WF_Sample[ID]->MODE-1 'L = 0, R = 1
                    SCHAN2 = 1 - SCHAN    '    1      0
                    FOR I = SCHAN2 TO WF_Sample[ID]->WAVEFILE.FACT - SCHAN - 1
                         NEW_AUDIODATA[I] = WF_Sample[ID]->WAVEFILE.AUDIODATA[I+I+1]
                    NEXT I
               END IF
               SWAP NEW_AUDIODATA,WF_Sample[ID]->WAVEFILE.AUDIODATA
               DELETE[] NEW_AUDIODATA
          END IF
     END IF
     'ANALYSE
     R = SOUNDRA.WF_POWER_ANALYSIS(WF_Sample[ID])
     R = SOUNDRA.WF_FREQ_ANALYSIS(WF_Sample[ID])
     'META
     WF_Sample[ID]->STATUS  = WFS_FREE
     WF_Sample[ID]->ON_DISK = TRUE
     MUTEXLOCK   LI_WF_Sample.MUTEX
     LI_WF_Sample.LENGTH += 1  
     MUTEXUNLOCK LI_WF_Sample.MUTEX
     RETURN ID
END FUNCTION

SUB DeleteSample(INDEX AS I32)
     IF INDEX > -1 AND INDEX < LI_WF_LIBRARY.LENGTH THEN
          MUTEXLOCK   LI_WF_Library.MUTEX
          FOR I AS I32 = INDEX TO LI_WF_LIBRARY.LENGTH - 2
               SWAP WF_Library[I],WF_Library[I+1]
               WF_Library[I]->ID = I
          NEXT I
          LI_WF_Library.LENGTH -= 1
          DELETE WF_Library[LI_WF_Library.LENGTH]
          MUTEXUNLOCK LI_WF_Library.MUTEX
          WF_LIBRARY_UPDATED = TRUE
     END IF
END SUB 

FUNCTION CreateFileCopy(FILE_PATH AS TXT, FILE_NAME AS TXT) AS TXT
     DIM AS I32 R,DOT_POS,N=2
     DIM AS TXT COPY_NAME, EXT = ""
     IF NOT(FILEEXISTS(FILE_PATH & "\" & FILE_NAME)) THEN RETURN ""
     'COUNT COPIES FOR NAMING
     COPY_NAME = FILE_NAME
     DOT_POS   = INSTRREV(COPY_NAME,".")
     IF DOT_POS THEN
          EXT       = MID(COPY_NAME,DOT_POS)
          COPY_NAME = MID(COPY_NAME,1,DOT_POS-1)
     END IF
     DOT_POS = INSTRREV(COPY_NAME,"#")
     IF DOT_POS THEN COPY_NAME = MID(COPY_NAME,1,DOT_POS-1)
     WHILE FILEEXISTS(FILE_PATH & "\" & COPY_NAME & "#" & STR(N) & EXT) 
          N += 1
     WEND
     COPY_NAME &= "#" & STR(N) & EXT
     R = FILECOPY(FILE_PATH & "\" & FILE_NAME,FILE_PATH & "\" & COPY_NAME)
     RETURN IIF(R=0,COPY_NAME,"")
END FUNCTION

FUNCTION LoadFilter(FILEPATH AS TXT, FILENAME AS TXT) AS I32
     DIM AS I32  R,ID
     DIM AS TXT  FULL_FILE_NAME = FILEPATH & "\" & FILENAME
     'MEMORY
     MT_LIST_DOUBLING(WF_Filter,LI_WF_Filter,_WF_CATALOG)
     IF R THEN RETURN R
     ID = LI_WF_Filter.LENGTH
     WF_Filter[ID]           = NEW _WF_CATALOG
     WF_Filter[ID]->FileName = FILENAME
     WF_Filter[ID]->Path     = FILEPATH
     WF_Filter[ID]->STATUS   = WFS_RESTRICTED
     'LOAD HEADER
     R = LoadWaveHdr(FULL_FILE_NAME, WF_Filter[ID]->WaveFile)
     IF R THEN RETURN UndoLoadWF(WF_Filter[ID], R)
     WF_Filter[ID]->CONTENT  = WFC_HEADER
     'LOAD CONTENT
     R = LoadWaveData(FULL_FILE_NAME, WF_Filter[ID]->WaveFile)
     IF R THEN RETURN UndoLoadWF(WF_Filter[ID], R)
     WF_Filter[ID]->ID       = ID
     WF_Filter[ID]->CONTENT  = WFC_FILE
     'CONVERT RESOLUTION
     IF WF_Filter[ID]->WaveFile.WaveFormat.FormatTag = _WAVE_FORMAT_INT THEN
          WF_Filter[ID]->STATUS = WFS_CONVERTING
          R = ConvertBitRes(WF_Filter[ID]->WaveFile)
          IF R THEN RETURN UndoLoadWF(WF_Filter[ID], R)
     END IF
     'META
     MUTEXLOCK   LI_WF_Filter.MUTEX
     LI_WF_Filter.LENGTH    += 1
     MUTEXUNLOCK LI_WF_Filter.MUTEX
     WF_Filter[ID]->Duration = WF_Filter[ID]->WaveFile.Fact / WF_Filter[ID]->WaveFile.WaveFormat.SampleRate
     WF_Filter[ID]->F0(0)    = 4 / WF_Filter[ID]->WaveFile.Fact
     WF_Filter[ID]->STATUS   = WFS_FREE
     WF_Filter[ID]->ON_DISK  = TRUE
     RETURN ID
END FUNCTION 

FUNCTION SaveWF(WF_PTR AS _WF_CATALOG PTR) AS I32
     DIM AS I32 R
     DIM AS TXT FULL_FILENAME
     FULL_FILENAME = WF_PTR->PATH & "\" & WF_PTR->FILENAME
     R = SaveWave(FULL_FILENAME, WF_PTR->WAVEFILE)
     IF R = 0 THEN WF_PTR->ON_DISK = TRUE
     RETURN R
END FUNCTION

SUB SET_MOD_SAMPLE(MOD_PTR AS SOUNDRA.MODULE PTR,WF_PTR AS _WF_CATALOG PTR,RECORD AS IOS=0)
     SOUNDRA.SETUP_MOD_WF(MOD_PTR,WF_PTR,RECORD)
     SELECT CASE MOD_PTR->FCT_ID
     CASE SOUNDRA.MM_SAMPLE
          Controls[ControlAccess[CS_SAMPLE_LIST_M]]->UPDATE_GTEXT(RECORD)
     CASE SOUNDRA.MM_SEQUENCER
          Controls[ControlAccess[CS_MELODY_LIST]]->UPDATE_GTEXT(RECORD)
     CASE SOUNDRA.MM_CONVOLUTION
          Controls[ControlAccess[CS_FILTER_LIST]]->UPDATE_GTEXT(RECORD)
     END SELECT
     SOUNDRA.REFRESH_FA(SOUNDRA.SELECTED_MODULE)
END SUB

FUNCTION SaveWFLibrary() AS I32
     DIM AS I32     R,FF
     DIM AS TXT     LIB_FILE_NAME = HOMEPATH & "\WF_Library.txt"
     MUTEXLOCK   MT_FF
     FF = FREEFILE
     R  = OPEN(LIB_FILE_NAME, FOR OUTPUT, AS #FF)
     MUTEXUNLOCK MT_FF
     IF R THEN RETURN -R
     FOR I AS I32 = 0 TO LI_WF_Library.LENGTH - 1
          IF WF_Library[I] ANDALSO WF_Library[I]->FileName <> "" THEN _
             PRINT #FF, WF_Library[I]->Path;",";WF_Library[I]->FileName
     NEXT I
     CLOSE #FF
     RETURN 0
END FUNCTION

FUNCTION LoadWFLibrary() AS I32
     STATIC AS I32  CC
     DIM AS I32     R,FF
     DIM AS TXT     LIB_FILE_NAME = HOMEPATH & "\WF_Library.txt", WF_PATH, WF_NAME, TMPSTR
     DIM AS IOS     BACKUP_FILE
     IF CC > 0 THEN RETURN 0
     CC += 1
     IF NOT(FILEEXISTS(LIB_FILE_NAME)) THEN RETURN 0
     MUTEXLOCK   MT_FF
     FF = FREEFILE
     R  = OPEN(LIB_FILE_NAME, FOR INPUT, AS #FF)
     MUTEXUNLOCK MT_FF
     IF R THEN RETURN -R
     DO UNTIL EOF(FF)
          INPUT #FF,WF_PATH
          INPUT #FF,WF_NAME
          WF_PATH = TRIM(WF_PATH)
          WF_NAME = TRIM(WF_NAME)
          IF WF_NAME <> "" THEN
               WF_PATH = FindPath(WF_PATH, WF_NAME, "\AUDIO")
               IF WF_PATH = "" THEN
                    '¤¤¤#148: "Could not find: "
                    TMPSTR = LNG_SET_EXT(148,"Could not find: ")
                    TMPSTR &= WF_NAME
                    TERMINAL(TMPSTR,MSG_WARNING)
                    BACKUP_FILE = TRUE
               ELSE
                    R = FIND_IN_WF_LIB(WF_PATH, WF_NAME, FALSE)
                    IF R > -1 THEN
                         '¤¤¤#149: " is already in library."
                         TMPSTR = LNG_SET_EXT(149," is already in library.")
                         TMPSTR = WF_NAME & TMPSTR
                         TERMINAL(TMPSTR,MSG_WARNING)
                    ELSE
                         R = LoadSample(WF_PATH, WF_NAME)
                         IF R < 0 THEN 
                              '¤¤¤#150: "Could not load: "
                              TMPSTR = LNG_SET_EXT(150,"Could not load: ")
                              TMPSTR &= WF_NAME
                              TERMINAL(TMPSTR,MSG_ERROR)
                              BACKUP_FILE = TRUE
                         END IF
                    END IF
               END IF
          END IF
     LOOP
     CLOSE #FF
     IF BACKUP_FILE THEN
          DIM AS TXT LIB_FILE_NAME_BKP = HOMEPATH & "\WF_Library_BACKUP_" & GetTimeStamp1970() & ".txt"
          R = FILECOPY(LIB_FILE_NAME,LIB_FILE_NAME_BKP)
          IF R THEN
               '¤¤¤#151: "Could not backup library file."
               TMPSTR = LNG_SET_EXT(151,"Could not backup library file.")
               TERMINAL(TMPSTR,MSG_ERROR)
          END IF
     END IF
     RETURN 0
END FUNCTION

'DISPATCHES ACTIONS FROM MULTI-THREAD MESSAGES RECEIVED IN GUI LOOP
FUNCTION SOUNDRA_ACTIONS(BYREF MSG AS THREAD_MESSAGE)                                     AS I32
     DIM AS I32               ID,R=0
     DIM AS TXT               TMPSTR, RESTART_MSG
     DIM AS THREAD_MESSAGE    TMP_MSG
     '¤¤¤#152: "Please restart for this change to take effect."
     TMPSTR = LNG_SET_EXT(152,"Please restart for this change to take effect.")
     RESTART_MSG = TMPSTR
     '* CURRENT ISSUE IS AN INEVITABLE BUT UNPREDICTABLE CRASH AFTER INTERFACE IS RE-CREATED.
     '* CAN'T FIND SOURCE OF MEMORY ISSUE SO USER IS TOLD TO RESTART.
     TMP_MSG.ACTION = CA_MD
     TMP_MSG.CODE   = _REMOTE_CTRL
     SELECT CASE MSG.CODE
     '-- SYSTEM -------------
     CASE _END_THREAD:
          '¤¤¤#153: "Thread #{$} ended."
          TMPSTR = LNG_SET_EXT(153,"Thread #{$} ended.")
          TMPSTR  = TXTINSERT(TMPSTR,STR(MSG.CALLER_ID))
          TERMINAL(TMPSTR,MSG_WARNING)
     CASE _ERROR:
          '¤¤¤#154: "Error with thread #{$}: "
          TMPSTR = LNG_SET_EXT(154,"Error with thread #{$}: ")
          TMPSTR  = TXTINSERT(TMPSTR,STR(MSG.thread_id))
          TMPSTR &= ERR2TXT(MSG.error_code)
          TERMINAL(TMPSTR,MSG_ERROR)
     '-- AUDIO --------------
     CASE _CP_DATA            'CAPTURE DATA RECEIVED
          DELETE[] CAST(F32 PTR,MSG.address)
     CASE _PB_DATA            'PLAYBACK DATA RECEIVED
          IF MSG.address > 0 THEN
               DIM AS F32 PTR TMP_PTR
               'TRANSFERT DATA PTR AND SIZE TO SCOPE ROUTINE
               MUTEXLOCK   SOUNDRA.MT_SCOPEDATA
               SOUNDRA.ScopeData_QWID += 1
               IF SOUNDRA.ScopeData_QWID = SOUNDRA.ScopeData_QLen THEN SOUNDRA.ScopeData_QWID = 0
               SOUNDRA.ScopeData_QSize(SOUNDRA.ScopeData_QWID) = MSG.X
               TMP_PTR = CAST(F32 PTR,MSG.address)
               IF SOUNDRA.ScopeDataQ(SOUNDRA.ScopeData_QWID) > 0 THEN _
                  DELETE[] SOUNDRA.ScopeDataQ(SOUNDRA.ScopeData_QWID)
               SOUNDRA.ScopeDataQ(SOUNDRA.ScopeData_QWID) = TMP_PTR
               MUTEXUNLOCK SOUNDRA.MT_SCOPEDATA
               'Controls[Windows[W_SYNTH]]->UPDATE_GTEXT()
               MSG.address = 0
          END IF
     CASE _PB_PP              'PLAYBACK PSEUDO-POWER RECEIVED
          IF MSG.address > 0 THEN
               DIM AS F32 PTR TMP_PTR
               TMP_PTR = CAST(F32 PTR,MSG.address)
               MUTEXLOCK   SOUNDRA.MT_SCOPEDATA
               IF SOUNDRA.PowerDisplayQ > 0 THEN
                    FOR I AS I32 = 0 TO 5
                         SOUNDRA.PowerDisplayQ[SOUNDRA.PowerDisplay_QPos+I] = TMP_PTR[I]
                    NEXT I
                    SOUNDRA.PowerDisplay_QPos += 6
                    IF SOUNDRA.PowerDisplay_QPos >= SOUNDRA.PowerDisplay_QSize THEN _
                       SOUNDRA.PowerDisplay_QPos  = 0
                    SOUNDRA.PowerDisplay_QCount += 1
               END IF
               MUTEXUNLOCK SOUNDRA.MT_SCOPEDATA
               DELETE[] TMP_PTR
          END IF
     CASE _START_PB           'Playback started
          SOUNDRA.WAIT4SYNTHUPDATE()
          PLAYING = TRUE
          ID = ControlAccess[CS_PlayP]
          Controls[ID]->STATE.POWER = PLAYING
          Controls[ID]->UPDATE_GSTATE()
     CASE _STOP_PB            'Playback stopped -> stop synth, update button
          SOUNDRA.WAIT4SYNTHUPDATE()
          PLAYING = FALSE
          ID = ControlAccess[CS_PlayP]
          Controls[ID]->STATE.POWER = PLAYING
          Controls[ID]->UPDATE_GSTATE()
     CASE _START_CP           'Capture started
          CAPTURING = TRUE
          ID = ControlAccess[CS_MicP]
          Controls[ID]->STATE.POWER = CAPTURING
          Controls[ID]->UPDATE_GSTATE()
     CASE _STOP_CP            'Capture :: Confirmation + timestamp -> stop capture, update button
          CAPTURING = FALSE
          ID = ControlAccess[CS_MicP]
          Controls[ID]->STATE.POWER = CAPTURING
          Controls[ID]->UPDATE_GSTATE()
     CASE _BUFFER_RESIZE
          SETTINGS.OUTPUT_BUFFER_LENGTH = 500*MSG.X/SETTINGS.SAMPLE_RATE
          SaveSettings()
          'CLICKS TABS AND SHOW MESSAGES
          ID = ControlAccess[CS_OUTBS]
          Controls[ID]->text.si = SETTINGS.OUTPUT_BUFFER_LENGTH-1
          '¤¤¤#155: " Playback buffer: {$}ms"
          TMPSTR = LNG_SET_EXT(155," Playback buffer: {$}ms")
          TMPSTR = TXTINSERT(TMPSTR,STR(Controls[ID]->text.si + 1))
          Controls[ID]->text.content = TMPSTR
          Controls[ID]->UPDATE_GTEXT()
          '¤¤¤#156: "Playback buffer length is now set to {$}ms."
          TMPSTR = LNG_SET_EXT(156,"Playback buffer length is now set to {$}ms.")
          TMPSTR = TXTINSERT(TMPSTR,STR(Controls[ID]->text.si + 1))
          TERMINAL(TMPSTR)
     CASE _STOP_RECORD
          RECORDING = FALSE
          Controls[ControlAccess[CS_Rec]]->state.POWER = FALSE
          Controls[ControlAccess[CS_Rec]]->UPDATE_GSTATE()
          '¤¤¤#157: "Recording has stopped because of an error:"
          TMPSTR = LNG_SET_EXT(157,"Recording has stopped because of an error:")
          TMPSTR &= " " & ERR2TXT(MSG.error_code)
          TERMINAL(TMPSTR)
     '-- VIDEO -------------
     CASE _ZOOM_CHANGE
          DIM AS F32 ZR = SETTINGS.ZOOM_FACTOR
          SETTINGS.ZOOM_FACTOR = MSG.X
          SaveSettings()
          TERMINAL(RESTART_MSG)
          /'
          STOP_VIDEO_THREAD()
          'SETTINGS
          SET_GUI_SIZE()
          ZR = SETTINGS.ZOOM_FACTOR / ZR
          'VIDEO
          Interface_PTR = SetScreen(8,SCR_W,SCR_H)
          R = CreateInterface(ZR)
          IF R THEN RETURN R
          R = START_VIDEO_THREAD()
          '¤¤¤#158: "Setting zoom to {$}x."
          TMPSTR = LNG_SET_EXT(158,"Setting zoom to {$}x.")
          TMPSTR = TXTINSERT(TMPSTR,STR(SETTINGS.ZOOM_FACTOR))
          TERMINAL(TMPSTR)
          'CLICK SETTINGS WIN TAB
          TMP_MSG.dest_id   = ControlAccess[CS_WIN_TAB0] + W_SETTINGS
          TMP_MSG.caller_id = 1
          SENDMTMSG(TMP_MSG,,FALSE)
          SelectModule(0)
          '/
     CASE _DRIVER_CHANGE
          STOP_VIDEO_THREAD()
          'SETTING
          DisplayDriverNum = MSG.X
          SETTINGS.VIDEO_DRIVER_INDEX = DisplayDriverNum
          SaveSettings()
          'VIDEO
          InterfaceSet  = TRUE
          Interface_PTR = SetScreen(8,SCR_W,SCR_H)
          R = START_VIDEO_THREAD(FALSE)
          '¤¤¤#159: "Display driver now set to: "
          TMPSTR = LNG_SET_EXT(159,"Display driver now set to: ")
          TMPSTR &= DISPLAY_DRIVER_NAME(DisplayDriverNum)
          TERMINAL(TMPSTR)
     CASE _RES_CHANGE
          DIM AS IOS W,H,N
          DIM AS F32 ZR = SETTINGS.ZOOM_FACTOR
          'SETTINGS - MONITORS
          N = MSG.Z
          NB_MONITORS = GetSystemMetrics(80)
          FLOOR(N,0)
          CEIL(N,NB_MONITORS-1)
          ID = ControlAccess[CS_NB_MONITORS]
          Controls[ID]->text.si      = N
          Controls[ID]->text.content = "x" & STR(N+1)
          Controls[ID]->UPDATE_GTEXT()
          'SETTINGS - RESOLUTION
          FLOOR(MSG.X,0)
          CEIL(MSG.X,NB_SCREENMODES-1)
          SETTINGS.MONITORS      = N+1
          SETTINGS.SCREEN_WIDTH  = SCREENMODES(MSG.X).X * SETTINGS.MONITORS
          SETTINGS.SCREEN_HEIGHT = SCREENMODES(MSG.X).Y
          SCREENMODE_ID = MSG.X
          CALC_ZMAX(SETTINGS.SCREEN_WIDTH*SETTINGS.MONITORS)
          SaveSettings()
          TMP_MSG.code    = _REMOTE_CTRL
          TMP_MSG.action  = CA_CLICK
          TMP_MSG.dest_id = ControlAccess[CS_ZCANCEL]
          SENDMTMSG(TMP_MSG)
          TERMINAL(RESTART_MSG)
          /'
          'VIDEO
          STOP_VIDEO_THREAD()
          Interface_PTR = SetScreen(8,SCR_W,SCR_H)
          ScreenControl FB.GET_DESKTOP_SIZE,DESK_W,DESK_H
          SET_GUI_SIZE()
          ZR = SETTINGS.ZOOM_FACTOR / ZR
          SaveSettings()
          R = CreateInterface(ZR)
          IF R THEN RETURN R
          R = START_VIDEO_THREAD()
          'CLICK SETTINGS WIN TAB
          TMP_MSG.dest_id   = ControlAccess[CS_WIN_TAB0] + W_SETTINGS
          TMP_MSG.caller_id = 1
          SENDMTMSG(TMP_MSG,,FALSE)
          SelectModule(0)
          '/
     CASE _WORK_OVERLOAD
          'UPDATE STATUS
          PLAYING = FALSE
          ID = ControlAccess[CS_PlayP]
          Controls[ID]->STATE.POWER = PLAYING
          Controls[ID]->UPDATE_GSTATE()
          'DISCONNECT & RESET SYNTH
          SOUNDRA.SU_DATA.RESET_FLAGS()
          SOUNDRA.SU_DATA.RESET_MODULES = TRUE
          LogAction(ACTION_SYNTH_DISCONNECT,SOUNDRA.ROOT_MODULE)
          SOUNDRA.ROOT_MODULE = 0
          SOUNDRA.PRECOMPILE(TRUE)
     CASE _LANGUAGE
          SaveSettings()
          TERMINAL(RESTART_MSG)
          /'
          STOP_VIDEO_THREAD()
          'LANGUAGE
          R = LOADLANGPACK(SETTINGS.LANGUAGE)
          IF R < 0 THEN 
               SETTINGS.LANGUAGE = 0
               TMPSTR  = "Could not find or load the selected language. Reverting to default (English)."
               TMPSTR &= ERR2TXT(R)
               TERMINAL(TMPSTR,MSG_ERROR)
          END IF
          'VIDEO
          Interface_PTR = SetScreen(8,SCR_W,SCR_H)
          R = CreateInterface()
          IF R THEN RETURN R
          R = START_VIDEO_THREAD()
          'CLICK SETTINGS WIN TAB
          TMP_MSG.dest_id   = ControlAccess[CS_WIN_TAB0] + W_SETTINGS
          TMP_MSG.caller_id = 1
          SENDMTMSG(TMP_MSG,,FALSE)
          SelectModule(0)
          '/
     END SELECT

     RETURN R
END FUNCTION 

FUNCTION DB_Ask(CallerID AS I32, Message AS TXT, Edit AS IOS, FieldContent AS TXT="", DB_TYPE AS I32, _
LI AS I32, EXT_ID AS I32)     AS I32
     DIM AS I32               ID, R=0
     'Already asked?
     IF CallerID = DB_DATA.CALLER_ID THEN RETURN 0
     'No request pending?
     IF DB_DATA.CALLER_ID = 0 THEN DB_DATA.CALLER_ID = CallerID
     'Cancel pending request if any
     ID = ControlAccess[CS_DIALOG]
     IF Controls[ID]->visible THEN
          IF NOT(CallerID = DB_DATA.CALLER_ID) THEN
               DIM AS THREAD_MESSAGE    TMP_MSG
               'Answer 'cancel' to last request
               DB_DATA.answer      = FALSE
               DB_DATA.is_answered = TRUE
               'Send CC_CTRL msg to main with callerID
               TMP_MSG.code        = _REMOTE_CTRL
               TMP_MSG.dest_id     = DB_DATA.CALLER_ID
               TMP_MSG.caller_id   = ControlAccess[CS_DIALOG]
               TMP_MSG.action      = CA_CLICK
               SENDMTMSG(TMP_MSG)
          END IF
     END IF
     'Set new request
     DB_DATA.CALLER_ID = CallerID
     DB_DATA.TYPE      = DB_TYPE
     DB_DATA.LI        = LI
     DB_DATA.EXT_ID    = EXT_ID
     IF Edit THEN
          Controls[ID+1]->w = Controls[ID+2]->w
          SetTextField(ID+2,FieldContent)
     ELSE
          Controls[ID+1]->w = 2*Controls[ID+2]->w
          Controls[ID+2]->VISIBLE = FALSE
          Controls[TFAccess[TF_Dialog]]->TEXT.content = FieldContent
          Controls[TFAccess[TF_Dialog]]->UPDATE_GTEXT()
     END IF
     Controls[ID+1]->text.content = "  " & Message  'QUESTION
     Controls[ID+1]->UPDATE_GTEXT()
     'SHOW DIALOG BOX AND HIDE CONTROL INFO LABEL & MSG BAR
     Controls[ControlAccess[CS_CTRL_INFO]]->visible = FALSE
     Controls[ControlAccess[CS_MSG_BAR]]->visible = FALSE
     Controls[ControlAccess[CS_DIALOG]]->visible = TRUE
     Controls[ControlAccess[CS_DIALOG]+3]->state.DISABLED = FALSE
     Controls[ControlAccess[CS_DIALOG]+3]->UPDATE_GSTATE()
     Controls[ControlAccess[CS_DIALOG]+4]->state.DISABLED = FALSE
     Controls[ControlAccess[CS_DIALOG]+4]->UPDATE_GSTATE()
     RETURN 0
END FUNCTION
FUNCTION DB_answer(OriginID AS I32,Answer AS IOS) AS I32
     'Visibility
     Controls[ControlAccess[CS_DIALOG]]->visible    = FALSE
     Controls[ControlAccess[CS_DIALOG]+3]->state.DISABLED = TRUE
     Controls[ControlAccess[CS_DIALOG]+3]->UPDATE_GSTATE()
     Controls[ControlAccess[CS_DIALOG]+4]->state.DISABLED = TRUE
     Controls[ControlAccess[CS_DIALOG]+4]->UPDATE_GSTATE()
     Controls[ControlAccess[CS_CTRL_INFO]]->visible = TRUE
     'Update answer to callerID
     DB_DATA.answer      = Answer
     DB_DATA.is_answered = TRUE
     'Controls[DB_CALLER_ID]->state.POWER            = FALSE
     'Controls[DB_CALLER_ID]->UPDATE_GSTATE()
     CR.ID = DEFAULT_CR_ID
     'Send _CTRL msg to main with callerID
     CTRL_MSG.code       = _REMOTE_CTRL
     CTRL_MSG.dest_id    = DB_DATA.CALLER_ID
     CTRL_MSG.caller_id  = OriginID
     CTRL_MSG.action     = CA_CLICK
     DB_DATA.CALLER_ID   = 0
     SENDMTMSG(CTRL_MSG)
     RETURN 0
END FUNCTION
FUNCTION DB_Cancel() AS I32
     DB_DATA.CALLER_ID = 0
     CR.ID = DEFAULT_CR_ID
     Controls[ControlAccess[CS_DIALOG]]->visible          = FALSE
     Controls[ControlAccess[CS_DIALOG]+3]->state.DISABLED = TRUE
     Controls[ControlAccess[CS_DIALOG]+3]->UPDATE_GSTATE()
     Controls[ControlAccess[CS_DIALOG]+4]->state.DISABLED = TRUE
     Controls[ControlAccess[CS_DIALOG]+4]->UPDATE_GSTATE()
     Controls[ControlAccess[CS_CTRL_INFO]]->visible       = TRUE
     RETURN 0
END FUNCTION

FUNCTION OpenBrowser(PATH AS TXT="",FILENAME AS TXT="") AS I32
     DIM AS I32 ID
     'TABS
     ID    = ControlAccess[CS_BROWSER_TAB0]
     Controls[ID]->state.power   = FALSE
     Controls[ID]->UPDATE_GSTATE()
     Controls[ID+1]->state.power = TRUE
     Controls[ID+1]->UPDATE_GSTATE()
     Controls[ID+2]->state.power = FALSE
     Controls[ID+2]->UPDATE_GSTATE()
     Controls[ID+3]->state.power = FALSE
     Controls[ID+3]->UPDATE_GSTATE()
     Controls[ID+4]->state.power = FALSE
     Controls[ID+4]->UPDATE_GSTATE()
     'WINDOWS
     Controls[ControlAccess[CS_FILES]]->VISIBLE   = TRUE
     Controls[ControlAccess[CS_CONSOLE]]->VISIBLE = FALSE
     Controls[ControlAccess[CS_SAMPLES]]->VISIBLE = FALSE
     Controls[ControlAccess[CS_MODULES]]->VISIBLE = FALSE
     'BUTTONS
     ID  = ControlAccess[CS_LI_ORDER]
     Controls[ID+BB_RENAME]->state.DISABLED = FALSE
     Controls[ID+BB_RENAME]->UPDATE_GSTATE()
     Controls[ID+BB_COPY]->state.DISABLED   = FALSE
     Controls[ID+BB_COPY]->UPDATE_GSTATE()
     Controls[ID+BB_DELETE]->state.DISABLED = FALSE
     Controls[ID+BB_DELETE]->UPDATE_GSTATE()
     Controls[ID+BB_LOAD]->state.DISABLED   = FALSE
     Controls[ID+BB_LOAD]->UPDATE_GSTATE()
     'BROWSE
     Browse(@FOLDER0,PATH,"*.*",FALSE,FALSE)
     'LIST ORDER
     ID = ControlAccess[CS_FILES]
     Controls[ControlAccess[CS_LI_ORDER]]->text.si = Controls[ID]->text.reversed_index + 1
     Controls[ControlAccess[CS_LI_ORDER]]->UPDATE_GTEXT()
     RETURN SelectFile(FILENAME)
END FUNCTION 

FUNCTION SelectFile(FILENAME AS TXT) AS I32
     DIM AS TXT FILENAME2 = TRIM(FILENAME)
     DIM AS I32 ID = ControlAccess[CS_FILE_LIST]
     IF FILENAME2 <> "" THEN
          'FIND FILE INDEX IN LIST
          DIM AS I32 I
          DIM AS TXT TMPSTR
          MUTEXLOCK  FOLDER0.LI_ITEM.MUTEX
          FOR I = 0 TO FOLDER0.LI_ITEM.LENGTH - 1
               TMPSTR = FOLDER0.GetName(I)
               IF FILENAME2 = TMPSTR THEN EXIT FOR
          NEXT I
          IF I = FOLDER0.LI_ITEM.LENGTH THEN I = -1
          MUTEXUNLOCK FOLDER0.LI_ITEM.MUTEX
          Controls[ID]->text.hi = -1
          Controls[ID]->text.si = I
     END IF
     RETURN AdjustListSelection(ID,FOLDER0.NbItems)
END FUNCTION

'FINDS THE VISIBLE LIST (BROWSER)
FUNCTION FindActiveList() AS I32
     DIM AS I32 ID = 0
     IF     Controls[ControlAccess[CS_CONSOLE]]->VISIBLE    THEN
          ID = ControlAccess[CS_CONSOLE_LIST]
          IF Controls[ID+MSG_SYSTEM]->VISIBLE  THEN ID += MSG_SYSTEM
          ID = ControlAccess[CS_CONSOLE_LIST]
          IF Controls[ID+MSG_ACTIONS]->VISIBLE THEN ID += MSG_ACTIONS
     ELSEIF Controls[ControlAccess[CS_FILES]]->VISIBLE      THEN
          ID = ControlAccess[CS_FILE_LIST]
     ELSEIF Controls[ControlAccess[CS_MODULES]]->VISIBLE    THEN
          ID = ControlAccess[CS_MODULE_LIST]
          IF Controls[ID+MOD_REFERENCE]->VISIBLE THEN ID += MOD_REFERENCE
          IF Controls[ID+MOD_LIBRARY]->VISIBLE   THEN ID += MOD_LIBRARY
     ELSEIF Controls[ControlAccess[CS_SAMPLES]]->VISIBLE    THEN
          ID = ControlAccess[CS_SAMPLE_LIST]
     ELSEIF Controls[ControlAccess[CS_MIDI_DEVS_LIST]]->VISIBLE     THEN
          ID = ControlAccess[CS_MIDI_DEVS_LIST]
     ELSEIF Controls[ControlAccess[CS_MIDICC]]->VISIBLE     THEN
          ID = ControlAccess[CS_MIDI_DEVS_LIST] + 1
     END IF
     RETURN ID
END FUNCTION 

SUB VIEW_MOD(MOD_ID AS I32)
     DIM AS I32 SY,SX,SW,SH,dX,dY, MX,MY,MW,MH
     IF MOD_ID > SOUNDRA.LI_MODULES.LENGTH - 1 OR MOD_ID < 0 THEN EXIT SUB
     IF SOUNDRA.MODULES[MOD_ID]->GUI_PTR = 0 THEN EXIT SUB
     SY = Controls[Windows[W_SYNTH]]->Y
     SX = Controls[Windows[W_SYNTH]]->X
     SW = Controls[Windows[W_SYNTH]]->W
     SH = Controls[Windows[W_SYNTH]]->H
     MX = SOUNDRA.MODULES[MOD_ID]->GUI_PTR->X
     MY = SOUNDRA.MODULES[MOD_ID]->GUI_PTR->Y
     MW = SOUNDRA.MODULES[MOD_ID]->GUI_PTR->W
     MH = SOUNDRA.MODULES[MOD_ID]->GUI_PTR->H
     IF    MY+MH > SY + SH    THEN
          dY = SY - MY + SH - MH
     ELSEIF MY < SY           THEN
          dY = SY - MY
     ELSEIF MX+MW > SX + SW   THEN
          dX = SX - MX + SW - MW
     ELSEIF MX < SX           THEN
          dX = SX - MX
     END IF
     IF ABS(dX) + ABS(dY) THEN
          DIM AS THREAD_MESSAGE TMP_MSG
          TMP_MSG.CODE    = _REMOTE_CTRL
          TMP_MSG.DEST_ID = Windows[W_SYNTH]
          TMP_MSG.TIME    = TIMER
          TMP_MSG.ACTION  = CA_SLIDE
          TMP_MSG.X       = dX
          TMP_MSG.Y       = dY
          SENDMTMSG(TMP_MSG)
     END IF
END SUB

FUNCTION CreateModule    (MOD_ID AS I32, X AS I32, Y AS I32, USE_REFERENCE AS IOS=TRUE)   AS I32
     DIM AS SOUNDRA.MODULE    PTR REF_MOD
     DIM AS TXT               TMPSTR
     MOD_ID = SOUNDRA.CREATE_MODULE(MOD_ID,USE_REFERENCE,REF_MOD)
     IF MOD_ID < 0 THEN
          '¤¤¤#160: "Error creating module: "
          TMPSTR = LNG_SET_EXT(160,"Error creating module: ")
          TMPSTR &= ERR2TXT(MOD_ID)
          TERMINAL(TMPSTR,MSG_ERROR)
     ELSE    
          DIM AS I32 R = CreateModControls(SOUNDRA.MODULES[MOD_ID],X,Y)
          IF R < 1 THEN 
               '¤¤¤#161: "Error creating module controls: "
               TMPSTR = LNG_SET_EXT(161,"Error creating module controls: ")
               TMPSTR &= ERR2TXT(R)
               TERMINAL(TMPSTR,MSG_ERROR)
          ELSE
               VIEW_MOD(MOD_ID)
               LogAction(ACTION_MOD_CREATE,SOUNDRA.MODULES[MOD_ID])
               SOUNDRA.REDRAW_CONNECTORS = TRUE
          END IF
     END IF
     RETURN MOD_ID
END FUNCTION

FUNCTION DeleteModule    (MOD_PTR AS SOUNDRA.MODULE PTR) AS I32
     DIM AS I32 ID,LI
     IF MOD_PTR = 0 THEN RETURN -_IFP
     'GET SELECTION INDEX
     ID = ControlAccess[CS_MODULE_LIST]+MOD_LIBRARY
     LI = Controls[ID]->text.si
     'DELETE MODULE
     IF MOD_PTR = SOUNDRA.SELECTED_MODULE THEN SelectModule(0)
     IF MOD_PTR = SOUNDRA.ROOT_MODULE THEN 
          Controls[ControlAccess[CS_SYNTH_OUT]]->state.POWER = FALSE
          Controls[ControlAccess[CS_SYNTH_OUT]]->UPDATE_GSTATE()
     END IF
     SOUNDRA.DELETE_MODULE(MOD_PTR)
     'UPDATE LIST SELECTION INDEX
     Controls[ID]->text.si = -1
     Controls[ID]->UPDATE_GTEXT(TRUE)
     LogAction(ACTION_MOD_DELETE,MOD_PTR)
     RETURN 0
END FUNCTION

SUB FlushActionLog()
     MUTEXLOCK   LI_LOG_ACTIONS.MUTEX
     FOR I AS I32 = 0 TO LI_LOG_ACTIONS.LENGTH-1
          IF LOG_ACTIONS[I] > 0 THEN DELETE LOG_ACTIONS[I] : LOG_ACTIONS[I] = 0
     NEXT I
     LI_LOG_ACTIONS.LENGTH = 0
     MUTEXUNLOCK LI_LOG_ACTIONS.MUTEX
END SUB

FUNCTION LogAction(ACTION AS I32, MOD_PTR AS SOUNDRA.MODULE PTR, PID AS I32=-1, _
OUTMOD_PTR AS SOUNDRA.MODULE PTR=0) AS I32
     DIM AS I32     R
     DIM AS TXT     TMPSTR, MOD_NAME = "", PORT_NAME = "", OUTMOD_NAME, TXT_SETAS
     DIM AS IOS     IS_SELF
     IF ACTION = -1 THEN RETURN 0
     IF MOD_PTR > 0 THEN MOD_NAME = MOD_PTR->Abbrev & " " & STR(MOD_PTR->TYPE_ID)
     '¤¤¤#162: "input '{$}' of"
     TMPSTR = LNG_SET_EXT(162,"input '{$}' of")
     IF PID > -1   THEN PORT_NAME = TXTINSERT(TMPSTR,MOD_PTR->Param(PID).VarName) & " "
     IF OUTMOD_PTR > 0 THEN
          IF OUTMOD_PTR = MOD_PTR THEN IS_SELF = TRUE
          OUTMOD_NAME = OUTMOD_PTR->Abbrev & " " & STR(OUTMOD_PTR->TYPE_ID)
     END IF
     '¤¤¤#163: "Set {$} as"
     TMPSTR = LNG_SET_EXT(163,"Set {$} as")
     TXT_SETAS = TMPSTR & " "
     SELECT CASE ACTION
     CASE ACTION_MOD_CREATE
          '¤¤¤#164: "Create "
          TMPSTR = LNG_SET_EXT(164,"Create ")
          TMPSTR &= MOD_NAME
     CASE ACTION_MOD_DELETE
          '¤¤¤#165: "Delete "
          TMPSTR = LNG_SET_EXT(165,"Delete ")
          TMPSTR &= MOD_NAME
     CASE ACTION_MOD_SLINK
          DIM AS IOS P = IIF(PID=-1,MOD_PTR->NBSC=1,MOD_PTR->PARAM(PID).LINK_SCHAN)
          '¤¤¤#166: "mono|stereo"
          TMPSTR = LNG_SET_EXT(166,"mono|stereo")
          TMPSTR = LR_WORD(TMPSTR,P)
          TMPSTR = TXTINSERT(TXT_SETAS,PORT_NAME & MOD_NAME) & TMPSTR
     CASE ACTION_MOD_VMAP
          '¤¤¤#167: "voice map source"
          TMPSTR = LNG_SET_EXT(167,"voice map source")
          TMPSTR = TXTINSERT(TXT_SETAS,PORT_NAME & MOD_NAME) & TMPSTR
     CASE ACTION_MOD_ENV
          '¤¤¤#168: "envelope"
          TMPSTR = LNG_SET_EXT(168,"envelope")
          TMPSTR = TXTINSERT(TXT_SETAS,MOD_NAME) & TMPSTR
     CASE ACTION_MOD_GLOBAL
          '¤¤¤#169: "global|local"
          TMPSTR = LNG_SET_EXT(169,"global|local")
          TMPSTR = LR_WORD(TMPSTR,MOD_PTR->PARAM(PID).USE_GLOBAL)
          TMPSTR = TXTINSERT(TXT_SETAS,PORT_NAME & MOD_NAME) & TMPSTR
     CASE ACTION_MOD_DOMAIN
          '¤¤¤#170: "energy|time"
          TMPSTR = LNG_SET_EXT(170,"energy|time")
          TMPSTR = LR_WORD(TMPSTR,MOD_PTR->PARAM(PID).DOMAIN)
          TMPSTR = TXTINSERT(TXT_SETAS,PORT_NAME & MOD_NAME) & TMPSTR
     CASE ACTION_MOD_CONNECT
          IF IS_SELF THEN 
               '¤¤¤#171: "Connect {$} to self"
               TMPSTR = LNG_SET_EXT(171,"Connect {$} to self")
               TMPSTR = TXTINSERT(TMPSTR,PORT_NAME & MOD_NAME)
          ELSE
               '¤¤¤#172: "Connect {$} to {$}"
               TMPSTR = LNG_SET_EXT(172,"Connect {$} to {$}")
               TMPSTR = TXTINSERT(TMPSTR,PORT_NAME & MOD_NAME)
               TMPSTR = TXTINSERT(TMPSTR,OUTMOD_NAME)
          END IF
     CASE ACTION_MOD_DISCONNECT
          IF IS_SELF THEN 
               '¤¤¤#173: "Disconnect {$} from self"
               TMPSTR = LNG_SET_EXT(173,"Disconnect {$} from self")
               TMPSTR = TXTINSERT(TMPSTR,PORT_NAME & MOD_NAME)
          ELSE
               '¤¤¤#174: "Disconnect {$} from {$}"
               TMPSTR = LNG_SET_EXT(174,"Disconnect {$} from {$}")
               TMPSTR = TXTINSERT(TMPSTR,PORT_NAME & MOD_NAME)
               TMPSTR = TXTINSERT(TMPSTR,OUTMOD_NAME)
          END IF
     CASE ACTION_SYNTH_CONNECT
          '¤¤¤#175: "Connect {$} to synth"
          TMPSTR = LNG_SET_EXT(175,"Connect {$} to synth")
          TMPSTR = TXTINSERT(TMPSTR,MOD_NAME)
     CASE ACTION_SYNTH_DISCONNECT
          '¤¤¤#176: "Disconnect {$} from synth"
          TMPSTR = LNG_SET_EXT(176,"Disconnect {$} from synth")
          TMPSTR = TXTINSERT(TMPSTR,MOD_NAME)
     CASE ACTION_MOD_POWER
          '¤¤¤#177: "Toggle power of {$}"
          TMPSTR = LNG_SET_EXT(177,"Toggle power of {$}")
          TMPSTR = TXTINSERT(TMPSTR,MOD_NAME)
     CASE ACTION_MOD_PLAY
          '¤¤¤#178: "Toggle play state of {$}"
          TMPSTR = LNG_SET_EXT(178,"Toggle play state of {$}")
          TMPSTR = TXTINSERT(TMPSTR,MOD_NAME)
     CASE ACTION_MOD_POLY
          '¤¤¤#179: "Toggle polyphony mode of {$}"
          TMPSTR = LNG_SET_EXT(179,"Toggle polyphony mode of {$}")
          TMPSTR = TXTINSERT(TMPSTR,MOD_NAME)
     CASE ELSE
          TMPSTR = ""
     END SELECT
     'APPEND MESSAGE
     IF TMPSTR <> "" THEN
          TMPSTR &= "."
          MT_LIST_DOUBLING(LOG_ACTIONS,LI_LOG_ACTIONS,_TEXTLINE)
          IF R = 0 THEN
               MUTEXLOCK   LI_LOG_ACTIONS.MUTEX
               LOG_ACTIONS[LI_LOG_ACTIONS.LENGTH]  = NEW _TEXTLINE 
               *LOG_ACTIONS[LI_LOG_ACTIONS.LENGTH] = STR(LI_LOG_ACTIONS.LENGTH) & "- " & TMPSTR
               LI_LOG_ACTIONS.LENGTH += 1
               MUTEXUNLOCK LI_LOG_ACTIONS.MUTEX
          END IF
     END IF
     RETURN R
END FUNCTION

SUB DISABLE_PARAM_FA()
     DIM AS I32 ID = ControlAccess[CS_PARAM_FA]
     FOR I AS I32 = 0 TO NB_FA_CONTROLS-1
          Controls[ID+I]->state.DISABLED = TRUE
          Controls[ID+I]->DATA = 0
          Controls[ID+I]->UPDATE_GDATA()
     NEXT I
     Controls[ControlAccess[CS_PARAM_GLOBAL]]->state.POWER = FALSE
     Controls[ControlAccess[CS_PARAM_GLOBAL]]->UPDATE_GSTATE()
     Controls[ControlAccess[CS_PARAM_DOMAIN]]->CLR = SWATCH0.FRM_OFF
     Controls[ControlAccess[CS_PARAM_DOMAIN]]->TEXT.CONTENT = " "
     Controls[ControlAccess[CS_PARAM_DOMAIN]]->UPDATE_GDATA()
     Controls[ControlAccess[CS_MIDI2]]->state.POWER_LEVEL = 0
     Controls[ControlAccess[CS_MIDI2]]->UPDATE_GSTATE()
END SUB

SUB ENABLE_PARAM_FA(FA_PTR AS FRACTIONAID.DATA PTR)
     DIM AS I32 ID = ControlAccess[CS_PARAM_FA]
     FOR I AS I32 = 0 TO NB_FA_CONTROLS-1
          Controls[ID+I]->DATA = FA_PTR
          Controls[ID+I]->state.DISABLED = FALSE
          Controls[ID+I]->UPDATE_GDATA()
     NEXT I
     Controls[ID+FAC_UNIT]->TEXT.content = FRACTIONAID.UNIT_SYMBOLS(FA_PTR->UNIT_TYPE,FA_PTR->UNIT_INDEX)
     Controls[ID+FAC_UNIT]->UPDATE_GTEXT()
END SUB

SUB SETUP_PARAM_FA(MOD_PTR AS SOUNDRA.MODULE PTR, PID AS I32)
     IF MOD_PTR = 0 THEN
          DISABLE_PARAM_FA()
          Controls[ControlAccess[CS_PARAM_SLINK]]->state.POWER    = FALSE
          Controls[ControlAccess[CS_PARAM_SLINK]]->UPDATE_GSTATE()
          Controls[ControlAccess[CS_PARAM_VHOST]]->state.POWER    = FALSE
          Controls[ControlAccess[CS_PARAM_VHOST]]->state.DISABLED = TRUE
          Controls[ControlAccess[CS_PARAM_VHOST]]->UPDATE_GSTATE()
     ELSEIF PID < 0 THEN
          DISABLE_PARAM_FA()
          Controls[ControlAccess[CS_PARAM_SLINK]]->state.POWER    = (MOD_PTR->NBSC = 1)
          Controls[ControlAccess[CS_PARAM_SLINK]]->UPDATE_GSTATE()
          Controls[ControlAccess[CS_PARAM_VHOST]]->state.POWER    = (MOD_PTR->VHOST_PID0 < 0)
          Controls[ControlAccess[CS_PARAM_VHOST]]->state.DISABLED = (MOD_PTR->NB_PORTS < 2) OR _
                                           (MOD_PTR->SYS_FLAGS(SOUNDRA.MSF_VHOST_MODE) = SOUNDRA.VM_SET) OR _
                                           (MOD_PTR->VOICE_SUPPORT = SOUNDRA.MONOPHONIC)
          Controls[ControlAccess[CS_PARAM_VHOST]]->UPDATE_GSTATE()
     ELSE
          DIM AS I32 ID
          DIM AS FRACTIONAID.DATA PTR FA_PTR = @MOD_PTR->PARAM(PID).FA
          IF MOD_PTR->PARAM(PID).PORT_ONLY = FALSE THEN
               ENABLE_PARAM_FA(FA_PTR)
          ELSE
               DISABLE_PARAM_FA()
          END IF
          Controls[ControlAccess[CS_PARAM_VHOST]]->state.POWER    = (MOD_PTR->VHOST_PID = PID)
          Controls[ControlAccess[CS_PARAM_VHOST]]->state.DISABLED = (MOD_PTR->NB_PORTS < 2)     OR _
                                                                     (PID > MOD_PTR->NB_PORTS-1) OR _
                                           (MOD_PTR->SYS_FLAGS(SOUNDRA.MSF_VHOST_MODE) = SOUNDRA.VM_SET) OR _
                                           (MOD_PTR->VOICE_SUPPORT = SOUNDRA.MONOPHONIC)
          Controls[ControlAccess[CS_PARAM_VHOST]]->UPDATE_GSTATE()
          Controls[ControlAccess[CS_PARAM_GLOBAL]]->state.POWER   = MOD_PTR->PARAM(PID).USE_GLOBAL
          Controls[ControlAccess[CS_PARAM_GLOBAL]]->UPDATE_GSTATE()
          Controls[ControlAccess[CS_PARAM_SLINK]]->state.POWER    = MOD_PTR->PARAM(PID).LINK_SCHAN
          Controls[ControlAccess[CS_PARAM_SLINK]]->UPDATE_GSTATE()
          Controls[ControlAccess[CS_MIDI2]]->state.POWER_LEVEL    = MOD_PTR->PARAM(PID).MIDI_CONTROLS
          Controls[ControlAccess[CS_MIDI2]]->UPDATE_GSTATE()
          ID = ControlAccess[CS_PARAM_DOMAIN]
          Controls[ID]->CLR = SOUNDRA.CONNECT_COLOR(MOD_PTR->PARAM(PID).DOMAIN)
          Controls[ID]->TEXT.CONTENT = IIF(MOD_PTR->PARAM(PID).DOMAIN = SOUNDRA.TIME_DOMAIN,"T","E")
          Controls[ID]->UPDATE_GDATA()
          SOUNDRA.REFRESH_FA(MOD_PTR)
     END IF
END SUB
          
SUB PARAM_LIST_UPDATE(PID AS I32)
     DIM AS I32 ID
     IF SOUNDRA.SELECTED_MODULE = 0 THEN 
          SOUNDRA.PARAM_LIST_SIZE = 0
          EXIT SUB
     END IF
     SOUNDRA.POPULATE_PARAM_LIST(SOUNDRA.SELECTED_MODULE)
     IF PID > -1 ANDALSO SOUNDRA.PARAM_LIST(PID).DISABLED THEN PID = -1
     SOUNDRA.SELECTED_MODULE->SELECTED_PID = PID
     'UPDATE DISPLAY
     ID = ControlAccess[CS_PARAM_LIST]
     Controls[ID]->TEXT.si      = PID
     Controls[ID]->TEXT.vi      = 0
     Controls[ID]->UPDATE_GTEXT(TRUE)
END SUB

SUB SelectModule(MOD_PTR AS SOUNDRA.MODULE PTR, PID AS I32 = -1)
     DIM AS I32               I,B,ID
     DIM AS THREAD_MESSAGE    TMP_MSG
     DIM AS IOS               INPUT_CLICK = (PID > -1)
     DIM AS SOUNDRA.MODULE    PTR SM 'FOR LIGHTER CODE

     IF DB_DATA.CALLER_ID = ControlAccess[CS_PARAM_FA] + FAC_EDIT THEN
          Controls[DB_DATA.CALLER_ID]->state.POWER = FALSE
          Controls[DB_DATA.CALLER_ID]->UPDATE_GSTATE()
          DB_cancel()
     END IF

     IF (MOD_PTR <> SOUNDRA.SELECTED_MODULE) OR (MOD_PTR = 0) THEN
          SM = SOUNDRA.SELECTED_MODULE
          'DESELECT LAST
          IF (SM > 0) THEN
               IF (SM->TYPE_ID > 0) ANDALSO (SM->GUI_PTR > 0) THEN 
                    SM->GUI_PTR->STATE.POWER = FALSE
                    SM->GUI_PTR->UPDATE_GSTATE()
               END IF
          END IF
          'SELECT NEW
          IF MOD_PTR > 0 THEN
               IF (MOD_PTR->TYPE_ID > 0) ANDALSO (MOD_PTR->GUI_PTR > 0) THEN
                    MOD_PTR->GUI_PTR->STATE.POWER = TRUE
                    MOD_PTR->GUI_PTR->UPDATE_GSTATE()
               END IF
          END IF
          Controls[ControlAccess[CS_FILTER_LIST]]->TEXT.si   = -1
          Controls[ControlAccess[CS_FILTER_LIST]]->UPDATE_GTEXT()
          Controls[ControlAccess[CS_SAMPLE_LIST_M]]->TEXT.si = -1
          Controls[ControlAccess[CS_SAMPLE_LIST_M]]->UPDATE_GTEXT()
          Controls[ControlAccess[CS_MELODY_LIST]]->TEXT.si   = -1
          Controls[ControlAccess[CS_MELODY_LIST]]->UPDATE_GTEXT()
     ELSEIF (MOD_PTR = SOUNDRA.SELECTED_MODULE) AND (MOD_PTR > 0) THEN 
          IF PID = MOD_PTR->SELECTED_PID THEN EXIT SUB 'ALREADY SELECTED - NO CHANGE
     END IF
     SOUNDRA.SELECTED_MODULE = MOD_PTR
     SM = SOUNDRA.SELECTED_MODULE

     'BROWSER BUTTON
     ID = ControlAccess[CS_LI_ORDER]
     B  = (ACTIVE_BROWSER_TAB = T_MESSAGES) AND (SM = 0)
     Controls[ID+BB_DELETE]->state.DISABLED = B
     Controls[ID+BB_DELETE]->UPDATE_GSTATE()

     'DESELECT LIST AND DISABLE MOD WINDOW
     IF SM = 0 THEN
          SOUNDRA.PARAM_LIST_SIZE = 0
          Controls[ControlAccess[CS_MODULE_LIST]+MOD_LIBRARY]->text.si   = -1
          Controls[ControlAccess[CS_MODULE_LIST]+MOD_LIBRARY]->UPDATE_GTEXT()
          Controls[ControlAccess[CS_MODULE_LIST]+MOD_REFERENCE]->text.si = -1
          Controls[ControlAccess[CS_MODULE_LIST]+MOD_REFERENCE]->UPDATE_GTEXT()
          SETUP_PARAM_FA(0,-1)
          Controls[ControlAccess[CS_TOOLS]]->state.DISABLED = TRUE
          Controls[ControlAccess[CS_TOOLS]]->UPDATE_GSTATE()
          EXIT SUB
     END IF
     Controls[ControlAccess[CS_TOOLS]]->state.DISABLED = FALSE
     Controls[ControlAccess[CS_TOOLS]]->UPDATE_GSTATE()

     'PORT INDEX
     IF PID = -1 THEN PID = SM->SELECTED_PID
     PARAM_LIST_UPDATE(PID)

     'MODULE CONTROLS - STATE & VISIBILITY
     ID = ControlAccess[CS_MOD_POWER]
     Controls[ID]->state.POWER                    = SM->FLAGS(SOUNDRA.MF_POWER)
     Controls[ID]->UPDATE_GSTATE()
     
     ID = ControlAccess[CS_MOD_CONTROLS]
     Controls[ID+MOD_PLAY]->state.POWER           = SM->FLAGS(SOUNDRA.MF_PLAY_POWER)
     Controls[ID+MOD_PLAY]->state.DISABLED        = NOT(SM->SYS_FLAGS(SOUNDRA.MSF_USEPLAY))
     Controls[ID+MOD_PLAY]->UPDATE_GSTATE()
     
     Controls[ID+MOD_HOLD]->VISIBLE               = SM->FCT_ID = SOUNDRA.MM_4IN1RANDOMIZER
     Controls[ID+MOD_HOLD]->state.POWER           = SM->FLAGS(SOUNDRA.MF_HOLD)
     Controls[ID+MOD_HOLD]->UPDATE_GSTATE()
     
     Controls[ID+MOD_REC]->state.DISABLED         = NOT(SM->SYS_FLAGS(SOUNDRA.MSF_USEREC)) OR _
                                                       (SM->PORT_TYPE > SOUNDRA.PORT_TYPES.DEFAULT)
     Controls[ID+MOD_REC]->state.POWER            = SM->FLAGS(SOUNDRA.MF_RECORD)
     Controls[ID+MOD_REC]->CLR              = IIF(SM->SYS_FLAGS(SOUNDRA.MSF_QREC_STOP)=TRUE,RGB(200,100,0),0)
     Controls[ID+MOD_REC]->UPDATE_GDATA()
     
     Controls[ID+MOD_LOOP]->state.DISABLED        = (SM->FCT_ID <> SOUNDRA.MM_SAMPLE)      AND _
                                                    (SM->FCT_ID <> SOUNDRA.MM_SEQUENCER)   AND _
                                                    (SM->FCT_ID <> SOUNDRA.MM_MELGEN)      AND _
                                                    (SM->FCT_ID <> SOUNDRA.MM_OSCILLATOR)
     Controls[ID+MOD_LOOP]->state.POWER           = SM->FLAGS(SOUNDRA.MF_LOOP)
     Controls[ID+MOD_LOOP]->CLR             = IIF(SM->SYS_FLAGS(SOUNDRA.MSF_LOOPING)=TRUE,RGB(200,100,0),0)
     Controls[ID+MOD_LOOP]->UPDATE_GDATA()
     
     Controls[ID+MOD_AUTOREWIND]->state.DISABLED  = NOT(SM->SYS_FLAGS(SOUNDRA.MSF_USEAUTOREWIND))
     Controls[ID+MOD_AUTOREWIND]->state.POWER     = SM->FLAGS(SOUNDRA.MF_AUTOREWIND)
     Controls[ID+MOD_AUTOREWIND]->UPDATE_GSTATE()
     
     Controls[ID+MOD_REWIND]->state.DISABLED      = NOT(SM->SYS_FLAGS(SOUNDRA.MSF_USEREWIND))
     Controls[ID+MOD_REWIND]->UPDATE_GSTATE()
     
     Controls[ID+MOD_SW1]->state.DISABLED         = NOT(SM->SYS_FLAGS(SOUNDRA.MSF_USESW1))
     Controls[ID+MOD_SW1]->text.si                = ABS(SM->FLAGS(SOUNDRA.MF_SWITCH1))
     Controls[ID+MOD_SW1]->UPDATE_GDATA(TRUE)
     
     Controls[ID+MOD_SW2]->state.DISABLED         = NOT(SM->SYS_FLAGS(SOUNDRA.MSF_USESW2))
     Controls[ID+MOD_SW2]->state.POWER            = SM->FLAGS(SOUNDRA.MF_SWITCH2)
     Controls[ID+MOD_SW2]->UPDATE_GSTATE()
     
     Controls[ID+MOD_ZERO]->state.DISABLED        = NOT(SM->SYS_FLAGS(SOUNDRA.MSF_USE0OUT))
     Controls[ID+MOD_ZERO]->state.POWER           = SM->FLAGS(SOUNDRA.MF_ZERO_OUT)
     Controls[ID+MOD_ZERO]->UPDATE_GSTATE()
     
     Controls[ID+MOD_DUAL]->state.DISABLED        = NOT(SM->SYS_FLAGS(SOUNDRA.MSF_USEDUAL))
     Controls[ID+MOD_DUAL]->state.POWER           = SM->FLAGS(SOUNDRA.MF_DUAL)
     Controls[ID+MOD_DUAL]->UPDATE_GSTATE()
     
     Controls[ID+MOD_ENVELOPE]->state.DISABLED    = (SM->FCT_ID = SOUNDRA.MM_ENVELOPE)     OR _
                                                    (SM->FCT_ID = SOUNDRA.MM_FOLLOWER)
     Controls[ID+MOD_ENVELOPE]->state.POWER       = SM->FLAGS(SOUNDRA.MF_ENVELOPE)
     Controls[ID+MOD_ENVELOPE]->UPDATE_GSTATE()
          'POLYPHONY
     ID = ControlAccess[CS_POLY]
     Controls[ID]->state.POWER    = SM->FLAGS(SOUNDRA.MF_VOICE_MODE)
     Controls[ID]->state.DISABLED = SM->VOICE_SUPPORT < SOUNDRA.ANYPHONIC
     Controls[ID]->UPDATE_GSTATE()
          'SEED
     ID = ControlAccess[CS_SEED]
     Controls[ID]->state.DISABLED = (SM->NB_PORTS = 0) OR (SM->TYPE_ID=0)
     Controls[ID]->state.POWER    = SM->FLAGS(SOUNDRA.MF_KICKSTART)
     Controls[ID]->UPDATE_GSTATE()
     
     'SET RECORD FUNCTION
     ID = ControlAccess[CS_MOD_CONTROLS] + MOD_REC
     IF Controls[ID]->state.DISABLED = FALSE THEN
          IF     SM->FCT_ID = SOUNDRA.MM_SAMPLE    THEN
               Controls[ID]->FCT_PTR = @ctrl_sample_record
          ELSEIF SM->FCT_ID = SOUNDRA.MM_SEQUENCER THEN
               Controls[ID]->FCT_PTR = @ctrl_melody_record
          ELSEIF SM->FCT_ID = SOUNDRA.MM_MELGEN    THEN
               Controls[ID]->FCT_PTR = @ctrl_melgen_record
          ELSE
               Controls[ID]->FCT_PTR = 0
          END IF
     END IF

     'TABS & LISTS
     IF     SM->FCT_ID = SOUNDRA.MM_OSCILLATOR THEN
          Controls[ControlAccess[CS_SHAPE_LIST]]->TEXT.si   = SM->GEN_ID
          Controls[ControlAccess[CS_SHAPE_LIST]]->UPDATE_GTEXT(TRUE)
          Controls[ControlAccess[CS_SHAPE_GRAPH]]->TEXT.si  = SM->GEN_ID
          Controls[ControlAccess[CS_SHAPE_GRAPH]]->UPDATE_GTEXT(TRUE)
     ELSEIF SM->FCT_ID = SOUNDRA.MM_KEYBOARD   THEN
          DIM AS TXT TMPSTR
          Controls[ControlAccess[CS_MM_DEVS_LIST]]->TEXT.si = SM->GEN_ID
          Controls[ControlAccess[CS_MM_DEVS_LIST]]->UPDATE_GTEXT(TRUE)
          ID = ControlAccess[CS_MIDIKB_CHAN]
          I  = SM->MIDI_CHAN
          Controls[ID]->TEXT.si  = I
          TMPSTR = Controls[ID]->text.content
          TMPSTR = MID(TMPSTR,1,LEN(TMPSTR)-2) & STR(I)
          IF I < 10 THEN TMPSTR &= " "
          Controls[ID]->text.content = TMPSTR
          Controls[ID]->UPDATE_GTEXT()
     ELSEIF SM->FCT_ID = SOUNDRA.MM_MELGEN THEN
          Controls[ControlAccess[CS_SP_LIST]]->TEXT.si = SM->GEN_ID
          Controls[ControlAccess[CS_SP_LIST]]->UPDATE_GTEXT(TRUE)
     ELSE
          Controls[ControlAccess[CS_FILTER_LIST]]->TEXT.si   = - 1
          Controls[ControlAccess[CS_FILTER_LIST]]->UPDATE_GTEXT()
          Controls[ControlAccess[CS_SAMPLE_LIST_M]]->TEXT.si = - 1
          Controls[ControlAccess[CS_SAMPLE_LIST_M]]->UPDATE_GTEXT()
          Controls[ControlAccess[CS_MELODY_LIST]]->TEXT.si   = - 1
          Controls[ControlAccess[CS_MELODY_LIST]]->UPDATE_GTEXT()
     END IF
     ID = ControlAccess[CS_PARAM_TAB0]
     Controls[ID+PARAM_SHAPES]->VISIBLE   = (SM->FCT_ID = SOUNDRA.MM_OSCILLATOR)
     Controls[ID+PARAM_SAMPLES]->VISIBLE  = (SM->FCT_ID = SOUNDRA.MM_SAMPLE)
     Controls[ID+PARAM_FILTERS]->VISIBLE  = (SM->FCT_ID = SOUNDRA.MM_CONVOLUTION)
     Controls[ID+PARAM_MELODIES]->VISIBLE = (SM->FCT_ID = SOUNDRA.MM_SEQUENCER)
     Controls[ID+PARAM_DEVICES]->VISIBLE  = (SM->FCT_ID = SOUNDRA.MM_KEYBOARD)
     Controls[ID+PARAM_SP]->VISIBLE       = (SM->FCT_ID = SOUNDRA.MM_MELGEN)
     IF INPUT_CLICK = TRUE    THEN SM->ACTIVE_TAB = 0
     IF Controls[ID]->fct_ptr THEN Controls[ID]->fct_ptr(CA_MD,0,0,ID+SM->ACTIVE_TAB)

     'SELECTION TITLE AND LIST INDEX
     I  = SM->TYPE_ID
     IF I = 0 THEN  'REFERENCE MODULE
          ID = ControlAccess[CS_MODULE_INFOS]
          Controls[ID]->TEXT.content = SM->Label
          Controls[ID]->UPDATE_GTEXT()
          ID = ControlAccess[CS_MODULE_LIST]+MOD_REFERENCE
          IF SOUNDRA.MM_MAP(Controls[ID]->text.si) <> SM->FCT_ID THEN
               DIM AS I32 II
               FOR II = 0 TO SOUNDRA.CATALOG_SIZE - 1
                    IF SOUNDRA.MM_MAP(II) = SM->FCT_ID THEN
                         Controls[ID]->text.si = II
                         EXIT FOR
                    END IF
               NEXT II
               Controls[ID]->UPDATE_GTEXT(TRUE)
          END IF
          'DESELECT IN LIBRARY LIST
          ID = ControlAccess[CS_MODULE_LIST]+MOD_LIBRARY
          Controls[ID]->text.si = -1
          Controls[ID]->UPDATE_GTEXT()
          ID = MOD_REFERENCE
     ELSE           'LIBRARY MODULE
          ID = ControlAccess[CS_MODULE_INFOS]
          Controls[ID]->TEXT.content = SM->Label & " " & STR(I)
          Controls[ID]->UPDATE_GTEXT()
          ID = ControlAccess[CS_MODULE_LIST]+MOD_LIBRARY
          IF Controls[ID]->text.si <> SM->MOD_ID THEN 
               Controls[ID]->text.si  = SM->MOD_ID
               Controls[ID]->UPDATE_GTEXT(TRUE)
          END IF
          'DESELECT IN REFERENCE LIST
          ID = ControlAccess[CS_MODULE_LIST]+MOD_REFERENCE
          Controls[ID]->text.si = -1
          Controls[ID]->UPDATE_GTEXT()
          ID = MOD_LIBRARY
     END IF
     IF ACTIVE_BROWSER_TAB <> T_MODULES THEN
          TMP_MSG.TIME      = TIMER
          TMP_MSG.code      = _REMOTE_CTRL
          TMP_MSG.dest_id   = ControlAccess[CS_BROWSER_TAB0] + T_MODULES
          TMP_MSG.ACTION    = CA_MD
          SENDMTMSG(TMP_MSG)
     END IF
     IF Controls[ControlAccess[CS_MODULE_LIST]+ID]->VISIBLE = FALSE THEN
          TMP_MSG.TIME      = TIMER
          TMP_MSG.code      = _REMOTE_CTRL
          TMP_MSG.dest_id   = ControlAccess[CS_MODULE_TAB0] + ID
          TMP_MSG.ACTION    = CA_MD
          SENDMTMSG(TMP_MSG)
     END IF

     'SPATIAL CHANNEL SELECTION
     ID = ControlAccess[CS_PARAM_SCHAN]
     Controls[ID]->text.si      = SM->SELECTED_SCHAN
     Controls[ID]->text.content = IIF(Controls[ID]->text.si=0,"L","R")
     Controls[ID]->UPDATE_GTEXT()

     'SET PARAM-SPECIFIC CONTROLS
     IF PID = -1 THEN
          SETUP_PARAM_FA(SM,-1)
     ELSE
          SETUP_PARAM_FA(SM,PID)
     END IF
END SUB

SUB UpdateTuningControls()
     DIM AS TXT TMPSTR,TXTN
     TXTN = STR(TUNINGS(TUNING_COPY).NB_KEYS)
     MUTEXLOCK   MT_TUNINGS(TUNING_COPY)
     Controls[ControlAccess[CS_TNG_NAME]]->TEXT.content    = TUNINGS(TUNING_COPY).FILENAME
     Controls[ControlAccess[CS_TNG_RFK]]->TEXT.content     = TUNINGS(TUNING_COPY).KEY_NAME(0)
     MUTEXUNLOCK MT_TUNINGS(TUNING_COPY)
     Controls[ControlAccess[CS_TNG_NAME]]->UPDATE_GTEXT()
     Controls[ControlAccess[CS_TNG_KEYS]]->TEXT.si         = -1
     Controls[ControlAccess[CS_TNG_KEYS]]->TEXT.FONT_SIZE  = DEF_FONT_SIZE + 3
     Controls[ControlAccess[CS_TNG_KEYS]]->UPDATE_GTEXT(TRUE)
     Controls[ControlAccess[CS_TNG_REL]]->STATE.DISABLED   = TRUE
     Controls[ControlAccess[CS_TNG_REL]]->UPDATE_GSTATE()
     Controls[ControlAccess[CS_TNG_REL]-1]->STATE.DISABLED = TRUE
     Controls[ControlAccess[CS_TNG_REL]-1]->UPDATE_GSTATE()
     Controls[ControlAccess[CS_TNG_REL]-2]->STATE.DISABLED = TRUE
     Controls[ControlAccess[CS_TNG_REL]-2]->UPDATE_GSTATE()
     Controls[ControlAccess[CS_TNG_ABS]]->STATE.DISABLED   = TRUE
     Controls[ControlAccess[CS_TNG_ABS]]->UPDATE_GSTATE()
     Controls[ControlAccess[CS_TNG_ABS]-1]->STATE.DISABLED = TRUE
     Controls[ControlAccess[CS_TNG_ABS]-1]->UPDATE_GSTATE()
     Controls[ControlAccess[CS_TNG_ABS]-2]->STATE.DISABLED = TRUE
     Controls[ControlAccess[CS_TNG_ABS]-2]->UPDATE_GSTATE()
     Controls[ControlAccess[CS_TNG_RFK]]->STATE.DISABLED   = TRUE
     Controls[ControlAccess[CS_TNG_RFK]]->UPDATE_GDATA()
     Controls[ControlAccess[CS_TNG_KR]]->STATE.DISABLED    = TRUE
     Controls[ControlAccess[CS_TNG_KR]]->UPDATE_GSTATE()
     '¤¤¤#180: " keys"
     TMPSTR = LNG_SET_EXT(180," keys")
     Controls[ControlAccess[CS_TNG_NBK_LBL]]->TEXT.content = TXTN & TMPSTR
     Controls[ControlAccess[CS_TNG_NBK_LBL]]->UPDATE_GTEXT()
     Controls[ControlAccess[CS_KB]]->UPDATE_GTEXT(TRUE)
     Controls[ControlAccess[CS_TNG_SP_LIST]]->TEXT.si      = -1
     Controls[ControlAccess[CS_TNG_SP_LIST]]->TEXT.vi      = 0
     Controls[ControlAccess[CS_TNG_SP_LIST]]->UPDATE_GTEXT(TRUE)
END SUB

FUNCTION SET_TEMPO            (BYREF VALUE AS F32) AS IOS
     FLOOR(VALUE,1)
     CEIL(VALUE,999)     
     SOUNDRA.GLOBAL_TEMPO = VALUE
     RETURN 0
END FUNCTION

FUNCTION SET_RF               (BYREF VALUE AS F32) AS IOS
     MUTEXLOCK MT_TUNINGS(TUNING_COPY)
     TUNINGS(TUNING_COPY).RF = VALUE
     VALUE = TUNINGS(TUNING_COPY).RF
     MUTEXUNLOCK MT_TUNINGS(TUNING_COPY)
     RETURN 0
END FUNCTION

FUNCTION SET_AK               (BYREF VALUE AS F32) AS IOS
     STATIC AS I32            CALL_COUNT
     CALL_COUNT += 1
     IF CALL_COUNT > 1 THEN RETURN 0 'AVOID INFINITE LOOP BECAUSE OF MUTUAL UPDATES
     DIM AS I32               KEY_NUM
     DIM AS FRACTIONAID.DATA  PTR FA_PTR
     'VALUE
     KEY_NUM = Controls[ControlAccess[CS_TNG_KEYS]]->TEXT.si
     IF KEY_NUM < 0 THEN RETURN 0
     MUTEXLOCK   MT_TUNINGS(TUNING_COPY)
     TUNINGS(TUNING_COPY).KEY_ABS(KEY_NUM) = VALUE
     VALUE = TUNINGS(TUNING_COPY).KEY_ABS(KEY_NUM)
     MUTEXUNLOCK MT_TUNINGS(TUNING_COPY)
     'UPDATE RK TOO
     FA_PTR = CAST(FRACTIONAID.DATA PTR, Controls[ControlAccess[CS_TNG_REL]]->DATA)
     IF FA_PTR > 0 THEN
          MUTEXLOCK   FA_PTR->MUTEX
          FA_PTR->IMPORT_VALUE(TUNINGS(TUNING_COPY).KEY_REL(KEY_NUM))
          MUTEXUNLOCK FA_PTR->MUTEX
     END IF
     CALL_COUNT = 0
     RETURN 0
END FUNCTION

FUNCTION SET_RK               (BYREF VALUE AS F32) AS IOS
     STATIC AS I32            CALL_COUNT
     CALL_COUNT+=1
     IF CALL_COUNT > 1 THEN RETURN 0 'AVOID INFINITE LOOP BECAUSE OF MUTUAL UPDATES
     DIM AS I32               KEY_NUM
     DIM AS FRACTIONAID.DATA  PTR FA_PTR 
     'VALUE
     KEY_NUM = Controls[ControlAccess[CS_TNG_KEYS]]->TEXT.si
     IF KEY_NUM < 0 THEN RETURN 0
     MUTEXLOCK   MT_TUNINGS(TUNING_COPY)
     TUNINGS(TUNING_COPY).KEY_REL(KEY_NUM) = VALUE
     VALUE = TUNINGS(TUNING_COPY).KEY_REL(KEY_NUM)
     MUTEXUNLOCK MT_TUNINGS(TUNING_COPY)
     'UPDATE AK TOO
     FA_PTR = CAST(FRACTIONAID.DATA PTR,Controls[ControlAccess[CS_TNG_ABS]]->DATA)
     IF FA_PTR > 0 THEN
          MUTEXLOCK   FA_PTR->MUTEX
          FA_PTR->IMPORT_VALUE(TUNINGS(TUNING_COPY).KEY_ABS(KEY_NUM))
          MUTEXUNLOCK FA_PTR->MUTEX
     END IF
     CALL_COUNT = 0
     RETURN 0
END FUNCTION

SUB SET_PARAM_FROM_KEY(KEY_NUM AS I32, TYPE_BB AS I32=0)
     DIM AS F32 F
     DIM AS TXT TMPSTR
     DIM AS I32 PID = Controls[ControlAccess[CS_PARAM_LIST]]->text.si
     IF (SOUNDRA.SELECTED_MODULE = 0) OR (PID = -1) THEN EXIT SUB
     IF TYPE_BB = FALSE THEN
          F = SOUNDRA.KB_GET_F(KEY_NUM) '
     ELSE
          F = SOUNDRA.BB_GET_F(KEY_NUM) '
     END IF
     IF SOUNDRA.SELECTED_MODULE->PARAM(PID).Factor <> SOUNDRA.F_MAX THEN
          F /= SOUNDRA.F_MAX
          IF SOUNDRA.SELECTED_MODULE->PARAM(PID).VarName = "A" THEN _
             F -= SOUNDRA.SELECTED_MODULE->PARAM(0).PortValue
     ELSE
          F *= SOUNDRA.SELECTED_MODULE->PARAM(PID).Factor / SOUNDRA.F_MAX
     END IF
     TMPSTR = STR(F)
     SetTextField(TFAccess[TF_Dialog],TMPSTR)
END SUB

SUB KB_MIDI_MANAGER(MIDI_KEY AS MIDI_CONTROL_MSG PTR) '! ONLY CALLED BY MIDI INPUT ROUTINE
     IF (PLAYING = FALSE) AND (SOUNDRA.VALUE_INPUT = FALSE) THEN
          MessageBar(KB_MSG)
     ELSE
          DIM AS I32 K,R
          SOUNDRA.VALUE_INPUT = (Controls[TFAccess[TF_Dialog]]->VISIBLE = TRUE) AND _
                                (Controls[ControlAccess[CS_PARAM_FA]+FAC_EDIT]->STATE.POWER = TRUE)
          K  = MIDI_KEY->NUM + SOUNDRA.MIDI_OFFSET(0).KEY
          K += SOUNDRA.MIDI_OFFSET(0).OCTAVE * TUNINGS(TUNING_CURRENT).NB_KEYS
          IF (K > -1) THEN
               IF MIDI_KEY->VALUE THEN
                    IF SOUNDRA.VALUE_INPUT THEN
                         SET_PARAM_FROM_KEY(K)
                    ELSE
                         SOUNDRA.KB_RELEASE(-1,K,MIDI_KEY->ID)                       '
                         R = SOUNDRA.KB_ADD(K,MIDI_KEY->VALUE/127,MIDI_KEY->ID)      '
                    END IF
               ELSEIF SOUNDRA.KB[SOUNDRA.KB_NUM]->LOCK_MODE = FALSE THEN 
                    SOUNDRA.KB_RELEASE(-1,K,MIDI_KEY->ID)   '
               END IF
          END IF
          Controls[ControlAccess[CS_KB]]->State.ACTIVE = TRUE
          Controls[ControlAccess[CS_KB]]->UPDATE_GSTATE()
     END IF
END SUB

SUB REFRESH_MIDI_LISTS(MM0 AS IOS=FALSE)
     'DESELECT LISTS
     Controls[ControlAccess[CS_MIDI_DEVS_LIST]]->text.si   = -1
     Controls[ControlAccess[CS_MIDI_DEVS_LIST]]->UPDATE_GTEXT(TRUE)
     Controls[ControlAccess[CS_MIDI_DEVS_LIST]+1]->text.si = -1
     Controls[ControlAccess[CS_MIDI_DEVS_LIST]+1]->UPDATE_GTEXT(TRUE)
     IF MM0 = TRUE THEN Controls[ControlAccess[CS_MM_DEVS_LIST]]->text.si = 0
     Controls[ControlAccess[CS_MM_DEVS_LIST]]->UPDATE_GTEXT(TRUE)
     'BACK TO DEVICE LIST, IF CONTROL LIST WAS VISIBLE
     IF Controls[ControlAccess[CS_MIDI_DEVS_LIST]+1]->VISIBLE = TRUE THEN
          Controls[ControlAccess[CS_MIDI_DEVS_LIST]]->VISIBLE   = TRUE
          Controls[ControlAccess[CS_MIDI_DEVS_LIST]+1]->VISIBLE = FALSE
     END IF
END SUB

SUB MIDIOPEN(dwInstance AS DWORD)
     DIM AS I32 ID
     MUTEXLOCK   SOUNDRA.MT_MIDICC
     ID = SOUNDRA.CHECK_MIDI_MAP_ID(dwInstance)
     IF ID > -1 THEN SOUNDRA.MIDI_MAP[ID]->CONNECTED = TRUE
     MUTEXUNLOCK SOUNDRA.MT_MIDICC
     REFRESH_MIDI_LISTS()
END SUB

SUB MIDICLOSE(dwInstance AS DWORD)
     DIM AS SOUNDRA.MIDI_CONTROL_MAP PTR MAP_PTR
     MUTEXLOCK   SOUNDRA.MT_MIDICC
     IF MIDI(dwInstance).CONTROL_MAP > 0 THEN
          MAP_PTR = CAST(SOUNDRA.MIDI_CONTROL_MAP PTR,MIDI(dwInstance).CONTROL_MAP)
          MAP_PTR->CONNECTED  = FALSE
     END IF
     MUTEXUNLOCK SOUNDRA.MT_MIDICC
     MIDI(dwInstance).RECYCLE = TRUE
     REFRESH_MIDI_LISTS()
END SUB

SUB MIDI_INPUT(hMidiIn AS HMIDIIN,wMsg AS UINT,dwInstance AS DWORD,dwParam1 AS DWORD,dwParam2 AS DWORD)
     SELECT CASE wMsg
	CASE MIM_OPEN:
          MIDIOPEN(dwInstance)
	CASE MIM_CLOSE:
          MIDICLOSE(dwInstance)
	CASE MIM_DATA:
          DIM AS MIDI_MESSAGE                MM
          DIM AS SOUNDRA.MIDI_CONTROL_MAP    PTR MAP_PTR
          IF MIDI(dwInstance).CONTROL_MAP > 0 THEN _
             MAP_PTR = CAST(SOUNDRA.MIDI_CONTROL_MAP PTR,MIDI(dwInstance).CONTROL_MAP)
          MM.INSTANCE = dwInstance 'THIS IS THE DEVICE REGISTRY INDEX WE PASSED ON MIDI OPEN
          MM.MESSAGE  = dwParam1
          MM.CLOCK    = dwParam2
          IF SETTINGS.DISPLAY_MIDIIN = TRUE THEN
               MUTEXLOCK   SOUNDRA.MT_MIDISTR
               SOUNDRA.MIDIIN_DISPLAY.MM      = MM
               SOUNDRA.MIDIIN_DISPLAY.MAP_PTR = MAP_PTR
               SOUNDRA.MIDIIN_DISPLAY.UPDATED = TRUE
               MUTEXUNLOCK SOUNDRA.MT_MIDISTR
          END IF
          IF (MM.CODE0 = MIDI_KEY_PRESS) OR (MM.CODE0 = MIDI_KEY_RELEASE) THEN 'KEYBOARD
               'MAP
               DIM AS I32 KB_ID = -1
               MUTEXLOCK   SOUNDRA.MT_MIDICC
               IF MAP_PTR > 0 THEN
                    IF MAP_PTR->KB_PTR(MM.CODE1) > 0 THEN
                         KB_ID = MAP_PTR->KB_PTR(MM.CODE1)->TYPE_ID '0..16
                    ELSEIF SOUNDRA.KB_MOD_PTR > 0 THEN 'SELECTED KB NUM
                         IF SOUNDRA.KB_MOD_PTR->GEN_ID    = 0 AND _
                           (SOUNDRA.KB_MOD_PTR->MIDI_CHAN = 0 OR _
                            SOUNDRA.KB_MOD_PTR->MIDI_CHAN = MM.CODE1+1) THEN _
                            KB_ID = SOUNDRA.KB_MOD_PTR->TYPE_ID
                    END IF
               END IF
               MUTEXUNLOCK SOUNDRA.MT_MIDICC
               'ACTION
               IF KB_ID > 0 THEN '-1 IS NO KB, 0 IS MASTER
                    DIM AS MIDI_CONTROL_MSG  MIDI_KEY
                    MIDI_KEY.NUM   = MM.PARAM0
                    MIDI_KEY.VALUE = IIF(MM.CODE0 = MIDI_KEY_PRESS,MM.PARAM1,0)
                    MIDI_KEY.CLOCK = MM.CLOCK
                    MIDI_KEY.ID    = KB_ID
                    KB_MIDI_MANAGER(@MIDI_KEY)
               END IF
          ELSEIF MM.CODE0 < 15 THEN
               DIM AS F32 V = MM.PARAM1 'VALUE
               'MAP CONTROL?
               IF SOUNDRA.MAP_MIDI > 0 THEN
                    MUTEXLOCK   SOUNDRA.MT_MIDICC
                    SOUNDRA.MAP_MIDI_CONTROL(dwInstance, MM.PARAM0, MM.CODE1, SOUNDRA.MAP_MIDI)
                    MUTEXUNLOCK SOUNDRA.MT_MIDICC
               END IF
               'CONTROLLER
               IF MM.CODE0 = MIDI_KEY_RELEASE THEN V = 0
               SOUNDRA.MIDI_CONTROLLER(dwInstance, MM.PARAM0, MM.CODE1, V, MM.CLOCK)
          END IF
	CASE MIM_LONGDATA:
	CASE MIM_ERROR:
	CASE MIM_LONGERROR:
	CASE MIM_MOREDATA:
	CASE ELSE:
	END SELECT
END SUB

'---- SESSIONS
FUNCTION SaveMelody(WF_PTR AS _WF_CATALOG PTR) AS I32
     DIM AS I32 R,FF,L
     DIM AS TXT TMPSTR, FULL_FILE_NAME = WF_PTR->PATH & "\" & WF_PTR->FILENAME

     IF WF_PTR = 0 THEN RETURN -_IFP
     WF_PTR->STATUS  = WFS_RESTRICTED
     
     MUTEXLOCK   MT_FF
     FF = FREEFILE
     R  = OPEN(FULL_FILE_NAME, FOR OUTPUT, AS #FF)
     CLOSE #FF
     FF = FREEFILE
     R  = OPEN(FULL_FILE_NAME, FOR BINARY, ACCESS WRITE, AS #FF)
     MUTEXUNLOCK MT_FF
     
     'HEADER
     TMPSTR = "SOUNDRA MELODY FILE V1.0"
     TXT2FILE(FF,TMPSTR)
     'DATA
     L = WF_PTR->WAVEFILE.DATASIZE
     R = PUT(FF,,L)
     R = PUT(FF,,*WF_PTR->WAVEFILE.AUDIODATA,L/4)
     
     CLOSE #FF
     WF_PTR->ON_DISK = TRUE
     WF_PTR->STATUS  = WFS_FREE
     RETURN 0
END FUNCTION

FUNCTION LoadMelody(PATH AS TXT, FILENAME AS TXT) AS I32
     DIM AS I32 ID,R,FF,L
     DIM AS F32 T
     DIM AS IOS BYTES_READ
     DIM AS TXT TMPSTR, FULL_FILE_NAME = PATH & "\" & FILENAME
     
     MUTEXLOCK   MT_FF
     FF = FREEFILE
     R  = OPEN(FULL_FILE_NAME, FOR BINARY, ACCESS READ, AS #FF)
     MUTEXUNLOCK MT_FF
     IF R THEN RETURN -R

     'Create a new WF_Catalog entry 
     MT_LIST_DOUBLING(WF_Melody,LI_WF_Melody,_WF_CATALOG)
     IF R THEN RETURN R
     ID = LI_WF_Melody.LENGTH
     WF_Melody[ID]           = NEW _WF_CATALOG
     WF_Melody[ID]->FileName = FILENAME
     WF_Melody[ID]->Path     = PATH
     WF_Melody[ID]->STATUS   = WFS_RESTRICTED
     WF_Melody[ID]->ID       = ID
     
     'GET VERSION
     FILE2TXT(FF,TMPSTR,L)
     IF TMPSTR <> "SOUNDRA MELODY FILE V1.0" THEN RETURN -_IFF
     
     'GET DATA
     R = GET(FF,,WF_Melody[ID]->WAVEFILE.DATASIZE)
     L = WF_Melody[ID]->WAVEFILE.DATASIZE/4
     IF WF_Melody[ID]->WAVEFILE.AUDIODATA > 0 THEN DELETE[] WF_Melody[ID]->WAVEFILE.AUDIODATA
     WF_Melody[ID]->WAVEFILE.AUDIODATA = 0
     WF_Melody[ID]->WAVEFILE.AUDIODATA = NEW F32[L]
     
     R = GET(#FF, ,*WF_Melody[ID]->WAVEFILE.AUDIODATA, L, BYTES_READ)
     CLOSE #FF

     IF (BYTES_READ <> WF_Melody[ID]->WAVEFILE.DATASIZE) THEN
          DELETE WF_Melody[ID]
          RETURN -_IFF
     END IF
     
     WF_Melody[ID]->DURATION = WF_Melody[ID]->WAVEFILE.AUDIODATA[SOUNDRA.SEQ.DURATION]
     WF_Melody[ID]->F0(0)    = WF_Melody[ID]->WAVEFILE.AUDIODATA[SOUNDRA.SEQ.F0] * SOUNDRA.F_MAX_1 'TEMPO
     L  = 4*(WF_Melody[ID]->WAVEFILE.AUDIODATA[SOUNDRA.SEQ.K]*SOUNDRA.SEQ.KEY_DATA_LENGTH + _
             SOUNDRA.SEQ.META_LENGTH)
     IF (L < WF_Melody[ID]->WaveFile.DataSize) THEN R = -_IFF
     IF R <> 0 THEN
          DELETE WF_Melody[ID]
          WF_Melody[ID] = 0
          RETURN -_IFF
     END IF

     WF_Melody[ID]->STATUS   = WFS_FREE
     WF_Melody[ID]->CONTENT  = WFC_FILE
     WF_Melody[ID]->ON_DISK  = TRUE

     MUTEXLOCK   LI_WF_Library.MUTEX
     LI_WF_Melody.LENGTH += 1
     MUTEXUNLOCK LI_WF_Library.MUTEX
     
     RETURN ID
END FUNCTION

FUNCTION SaveSession(SESSION_PATH AS TXT, SESSION_FILE AS TXT) AS I32
     DIM AS I32     I,J,R,N, FF, ROOT_MOD_ID, NB_MODS, NB_MODS_TOTAL, XSO,YSO
     DIM AS TXT     TMPSTR, STR_SAVEERR, TMP_DIR, FILE_PATH, FILE_NICK, FILE_NAME, FULL_FILE_NAME, BKP_FILE
     DIM AS SOUNDRA.MODULE_XP EXPORTED_MODULE
     DIM AS F32     ZR

     '¤¤¤#181: "Could not save {$}: "
     TMPSTR = LNG_SET_EXT(181,"Could not save {$}: ")
     STR_SAVEERR = TMPSTR
     
     'NEW FILE
     IF SESSION_FILE = "" THEN
          FILE_PATH = HOMEPATH & "\SYNTHS"
          FILE_NICK = "SS" & TemporalStamp()
          FILE_NAME = FILE_NICK & ".soundra"
     ELSE
          FILE_PATH = SESSION_PATH
          FILE_NAME = SESSION_FILE
          J = INSTRREV(FILE_NAME,".")
          FILE_NICK = MID(FILE_NAME,1,J-1)
     END IF
     FULL_FILE_NAME = FILE_PATH & "\" & FILE_NAME
     
     'SESSION PATH - CHECK/CREATE FOLDER
     TMP_DIR = CURDIR
     IF CHDIR(FILE_PATH) THEN 
          R = MKDIR(FILE_PATH)
          IF R THEN RETURN R
     END IF
     CHDIR(TMP_DIR)

     'BACKUP FILE IN CASE OF ERROR
     IF FILEEXISTS(FULL_FILE_NAME) THEN
          BKP_FILE = FILE_PATH & "\" & FILE_NICK & "_BACKUP.soundra"
          FILECOPY(FULL_FILE_NAME,BKP_FILE)
     END IF
     
     'CLEAR OR CREATE SESSION FILE
     MUTEXLOCK   MT_FF
     FF = FREEFILE
     R  = OPEN(FULL_FILE_NAME, FOR OUTPUT, AS #FF)
     MUTEXUNLOCK MT_FF
     IF R THEN RETURN R
     CLOSE #FF

     MUTEXLOCK   MT_FF
     FF = FREEFILE
     R  = OPEN(FULL_FILE_NAME, FOR BINARY, ACCESS WRITE, AS #FF)
     MUTEXUNLOCK MT_FF
     IF R THEN RETURN R

     'HEADER - PLAN FOR 1.2: COMPRESSED FILE W/ TEMPORARY UNCOMPRESSED FILE
     TMPSTR = "SOUNDRA SYNTH FILE V1.1"
     TXT2FILE(FF,TMPSTR)

     'TUNING FILE
     MUTEXLOCK   MT_TUNINGS(TUNING_CURRENT)
     TMPSTR = TUNINGS(TUNING_CURRENT).FILEPATH
     TXT2FILE(FF,TMPSTR)
     TMPSTR = TUNINGS(TUNING_CURRENT).FILENAME
     TXT2FILE(FF,TMPSTR)
     MUTEXUNLOCK MT_TUNINGS(TUNING_CURRENT)
     
     'VOLUMES
     R = PUT(FF,,VOLUME_IN)
     R = PUT(FF,,VOLUME_OUT)

     'SAMPLES IN RAM
     MUTEXLOCK   LI_WF_Sample.MUTEX
     R = PUT(FF,,LI_WF_Sample.LENGTH)
     'FOR EACH FILE
     FOR I = 0 TO LI_WF_Sample.LENGTH-1
          'SAVE IF NOT ON DISK
          IF WF_Sample[I]->ON_DISK = FALSE THEN
               R = SaveWF(WF_Sample[I])
               IF R THEN 
                    TMPSTR  = STR_SAVEERR
                    TMPSTR  = TXTINSERT(TMPSTR,WF_Sample[I]->FILENAME)
                    TMPSTR &= ERR2TXT(R)
                    TERMINAL(TMPSTR,MSG_ERROR)
               ELSE
                    R = FIND_IN_WF_LIB(WF_Sample[I]->PATH, WF_Sample[I]->FILENAME, FALSE)
                    IF R = -1 THEN R = LoadSample(WF_Sample[I]->PATH, WF_Sample[I]->FILENAME)
               END IF
          END IF
          'SAVE MODE AND FILENAME
          R = PUT(FF,,WF_Sample[I]->MODE)
          TMPSTR = WF_Sample[I]->PATH
          TXT2FILE(FF,TMPSTR)
          TMPSTR = WF_Sample[I]->FILENAME
          TXT2FILE(FF,TMPSTR)
     NEXT I
     MUTEXUNLOCK LI_WF_Sample.MUTEX

     'FILTERS
     MUTEXLOCK   LI_WF_Filter.MUTEX
     R = PUT(FF,,LI_WF_Filter.LENGTH)
     'FOR EACH FILE
     FOR I = 0 TO LI_WF_Filter.LENGTH-1
          IF WF_Filter[I]->ON_DISK = FALSE THEN
               R = SaveWF(WF_Filter[I])
               IF R THEN
                    TMPSTR  = STR_SAVEERR
                    TMPSTR  = TXTINSERT(TMPSTR,WF_Filter[I]->FILENAME)
                    TMPSTR &= ERR2TXT(R)
                    TERMINAL(TMPSTR,MSG_ERROR)
               END IF
          END IF
          R = PUT(FF,,WF_Filter[I]->MODE)
          TMPSTR = WF_Filter[I]->PATH
          TXT2FILE(FF,TMPSTR)
          TMPSTR = WF_Filter[I]->FILENAME
          TXT2FILE(FF,TMPSTR)
     NEXT I
     MUTEXUNLOCK LI_WF_Filter.MUTEX

     'MELODIES
     MUTEXLOCK   LI_WF_Melody.MUTEX
     R = PUT(FF,,LI_WF_Melody.LENGTH)
     'FOR EACH FILE
     FOR I = 0 TO LI_WF_Melody.LENGTH-1
          IF WF_Melody[I]->ON_DISK = FALSE THEN
               R = SaveMelody(WF_Melody[I])
               IF R THEN
                    TMPSTR  = STR_SAVEERR
                    TMPSTR  = TXTINSERT(TMPSTR,WF_Melody[I]->FILENAME)
                    TMPSTR &= ERR2TXT(R)
                    TERMINAL(TMPSTR,MSG_ERROR)
               END IF
          END IF
          TMPSTR = WF_Melody[I]->PATH
          TXT2FILE(FF,TMPSTR)
          TMPSTR = WF_Melody[I]->FILENAME
          TXT2FILE(FF,TMPSTR)
     NEXT I
     MUTEXUNLOCK LI_WF_Melody.MUTEX

     'BOARDS
     N = SOUNDRA.NB_KEYBOARDS_MAX
     R = PUT(FF,,N)
     FOR I = 0 TO SOUNDRA.NB_KEYBOARDS_MAX - 1
          IF PLAYING THEN 
               DIM AS F64 T = TIMER
               WHILE SOUNDRA.BB[I]->REMAP = TRUE
                    IF TIMER - T > 1 THEN EXIT WHILE
               WEND
          END IF
          R = PUT(FF,,*SOUNDRA.BB[I])
     NEXT I
     FOR I = 0 TO SOUNDRA.NB_KEYBOARDS_MAX - 1
          IF PLAYING THEN 
               DIM AS F64 T = TIMER
               WHILE SOUNDRA.KB[I]->REMAP = TRUE
                    IF TIMER - T > 1 THEN EXIT WHILE
               WEND
          END IF
          R = PUT(FF,,*SOUNDRA.KB[I])
     NEXT I

     'MODULES
     MUTEXLOCK   SOUNDRA.LI_MODULES.MUTEX
     NB_MODS = SOUNDRA.LI_MODULES.LENGTH
     MUTEXUNLOCK SOUNDRA.LI_MODULES.MUTEX
     ROOT_MOD_ID = -1
     IF SOUNDRA.ROOT_MODULE THEN ROOT_MOD_ID = SOUNDRA.ROOT_MODULE->MOD_ID
     R = PUT(FF,,ROOT_MOD_ID)
     NB_MODS_TOTAL = NB_MODS + SOUNDRA.CATALOG_SIZE
     R = PUT(FF,,NB_MODS_TOTAL)
     XSO = Controls[ControlAccess[CS_SYNTH_OUT]]->X
     YSO = Controls[ControlAccess[CS_SYNTH_OUT]]->Y
     ZR  = 1 / SETTINGS.ZOOM_FACTOR_CURRENT
     MUTEXLOCK   SOUNDRA.LI_MODULES.MUTEX
     FOR I = 0 TO NB_MODS - 1
          EXPORTED_MODULE    = SOUNDRA.MODULES[I]
          EXPORTED_MODULE.X  = ZR*(EXPORTED_MODULE.X - XSO) + XSO - SOUNDRA.PLANE_X
          EXPORTED_MODULE.Y  = ZR*(EXPORTED_MODULE.Y - YSO) + YSO - SOUNDRA.PLANE_Y
          R = PUT(FF,,EXPORTED_MODULE)
     NEXT I
     MUTEXUNLOCK SOUNDRA.LI_MODULES.MUTEX
     FOR I = 0 TO SOUNDRA.CATALOG_SIZE - 1
          EXPORTED_MODULE    = @SOUNDRA.REFERENCE_MODULE(I)
          EXPORTED_MODULE.X  = 0
          EXPORTED_MODULE.Y  = 0
          R = PUT(FF,,EXPORTED_MODULE)
     NEXT I

     R = SOUNDRA.SAVE_MIDI_MAPS11(FF)
     
     CLOSE #FF

     IF FILE_PATH <> SETTINGS.SESSION_PATH OR FILE_NAME <> SETTINGS.SESSION_FILE THEN
          SETTINGS.SESSION_PATH = FILE_PATH
          SETTINGS.SESSION_FILE = FILE_NAME
          SETTINGS.SESSION_NAME = FILE_NICK
          SaveSettings()
     END IF
     
     IF InterfaceSet THEN
          I = ControlAccess[CS_BROWSER_TAB0] + T_SESSIONS
          Controls[I]->text.content = " " & FILE_NICK
          Controls[I]->UPDATE_GTEXT()
     END IF

     IF BKP_FILE <> "" ANDALSO FILEEXISTS(BKP_FILE) THEN KILL(BKP_FILE)
     RETURN 0
END FUNCTION

FUNCTION CreateModControls(MOD_PTR AS SOUNDRA.MODULE PTR,X AS I32,Y AS I32)               AS I32
     DIM AS I32 CID,MOD_TYPE,TYPE_ID
     IF MOD_PTR = 0 THEN RETURN -_IFP
     MOD_TYPE = MOD_PTR->FCT_ID
     TYPE_ID  = MOD_PTR->TYPE_ID
     IF (MOD_TYPE > SOUNDRA.CATALOG_SIZE-1) OR _
        (SOUNDRA.REFERENCE_MODULE(MOD_TYPE).GUI_PTR = 0) THEN RETURN -_IFP
     CID = copy_ctrl(SOUNDRA.REFERENCE_MODULE(MOD_TYPE).GUI_PTR,TRUE,Windows[W_SYNTH])
     IF CID > 0 THEN
          DIM AS I32 PATCH_ID,CON_ID
          DIM AS TXT TMPSTR = SOUNDRA.REFERENCE_MODULE(MOD_TYPE).Abbrev & " " & STR(TYPE_ID)
          IF MOD_TYPE = SOUNDRA.MM_OSCILLATOR THEN _
             TMPSTR &= " : " & Abbreviate(SOUNDRA.GENERATOR_NAME(MOD_PTR->GEN_ID),TRUE)
          Controls[CID]->TEXT.CONTENT = TMPSTR
          Controls[CID]->INDEX        = MOD_PTR->MOD_ID
          Controls[CID]->DATA         = MOD_PTR
          Controls[CID]->X            = X
          Controls[CID]->Y            = Y
          Controls[CID]->STATE.POWER  = FALSE
          ctrl_update(CID,TRUE)
          MOD_PTR->GUI_PTR = Controls[CID]
          'SET GROUP VISIBILITY ACCORDING TO SOUNDRA.PATCH_MODE
          PATCH_ID = Controls[CID]->family.children[0]
          CON_ID   = Controls[CID]->family.children[1]
          Controls[PATCH_ID]->VISIBLE  = SOUNDRA.PATCH_MODE
          Controls[CON_ID]->VISIBLE    = NOT(SOUNDRA.PATCH_MODE)
          Controls[CID]->VISIBLE       = TRUE
          Controls[ControlAccess[CS_MODULE_LIST]+MOD_LIBRARY]->UPDATE_GTEXT(TRUE)
     END IF
     RETURN CID
END FUNCTION

FUNCTION LoadSession(SESSION_PATH AS TXT,SESSION_FILE AS TXT) AS I32
     DIM AS I32     I,J,R,L,FF, ID,NB_SAMPLES, NB_FILTERS, NB_MELODIES, NB_BOARDS, ROOT_MOD_ID, NB_MODS
     DIM AS TXT     SESSION_NAME, FULL_FILE_NAME = SESSION_PATH & "\" & SESSION_FILE
     DIM AS TXT     TMPSTR, FILE_PATH, FILE_NAME, VERSION
     DIM AS SAMPLE_LIST_ITEM  PTR SAMPLE_LIST, FILTER_LIST, MELODY_LIST
     DIM AS SOUNDRA.MODULE_XP PTR PTR XP_MOD
     DIM AS _WF_CATALOG       PTR WF_PTR
     DIM AS I32               RETURN_CODE,WF_ID,NB_MASTERS,XSO,YSO
     DIM AS IOS               ISTUNINGLOADED,ISNEWER,DO_PATCH
     DIM AS F32               ZR
     DIM AS UI32              FLAG
     DIM AS F64               D
     DIM AS SOUNDRA.MODULE    PTR ROOT 'COPY

     SelectModule(0)

     'FILE
     IF SESSION_FILE = "" THEN RETURN -_IFP
     MUTEXLOCK   MT_FF
     FF = FREEFILE
     R  = OPEN(FULL_FILE_NAME, FOR BINARY, ACCESS READ, AS #FF)
     MUTEXUNLOCK MT_FF
     IF R THEN RETURN -R
     IF LOF(FF) < 53000 THEN CLOSE #FF : RETURN -_IFF

     D = FileDateTime(FULL_FILE_NAME)
     IF D < 45901 THEN DO_PATCH = TRUE
     'LOAD FILE ---------------------------------------------------------------------------------------

     'HEADER
     FILE2TXT(FF,TMPSTR,L)
     IF MID(TMPSTR,1,20) <> "SOUNDRA SYNTH FILE V" THEN CLOSE #FF : RETURN -_IFF
     VERSION = (MID(TMPSTR,21,3))
     '### VERSION 1 SUPPORT
     IF VERSION <> "1.0" AND VERSION <> "1.1" THEN CLOSE #FF : RETURN -_IFF

     'UNPLUG SYNTH     
     SOUNDRA.DO_PRECOMPILE(TRUE)

     'TUNING FILE
     FILE2TXT(FF,FILE_PATH,L)
     FILE2TXT(FF,FILE_NAME,L)
     FILE_PATH = FindPath(FILE_PATH,FILE_NAME,"\TUNINGS")
     IF FILE_PATH <> "" THEN
          'LOAD INTO COPY
          MUTEXLOCK   MT_TUNINGS(TUNING_COPY)
          R = LoadTuning(@TUNINGS(TUNING_COPY),FILE_PATH,FILE_NAME)
          MUTEXUNLOCK MT_TUNINGS(TUNING_COPY)
          IF R = 0 THEN 
               ISTUNINGLOADED = TRUE
          ELSE
               '¤¤¤#182: "Error loading tuning file: "
               TMPSTR = LNG_SET_EXT(182,"Error loading tuning file: ")
               TMPSTR &= ERR2TXT(R)
               TERMINAL(TMPSTR,MSG_ERROR)
          END IF
     END IF

     'VOLUMES
     R = GET(FF,,VOLUME_IN)
     R = GET(FF,,VOLUME_OUT)
          'IN
     FLOOR(VOLUME_IN,dU32)
     CEIL(VOLUME_IN,2)
     Controls[ControlAccess[CS_MicM]]->text.content=STR(ROUND(FRACTIONAID.U2DB(VOLUME_IN,0,,FLAG),2)) & "dB"
     Controls[ControlAccess[CS_MicM]]->UPDATE_GTEXT()
          'OUT
     FLOOR(VOLUME_OUT,dU32)
     CEIL(VOLUME_OUT,1)
     Controls[ControlAccess[CS_PlayM]]->text.content=STR(ROUND(FRACTIONAID.U2DB(VOLUME_OUT,0,,FLAG),2)) & "dB"
     Controls[ControlAccess[CS_PlayM]]->UPDATE_GTEXT()
          'INFORM AUDIO THREAD
     CTRL_MSG.code = _SET_OUTPUT_VOLUME
     CTRL_MSG.x    = VOLUME_OUT
     SENDMTMSG(CTRL_MSG,JIGSAW_AUDIO.THREAD_ID)

     'SAMPLES
     R = GET(FF,,NB_SAMPLES)
     IF NB_SAMPLES > 0 THEN
          SAMPLE_LIST = NEW SAMPLE_LIST_ITEM[NB_SAMPLES]
          FOR I = 0 TO NB_SAMPLES - 1
               R = GET(FF,,SAMPLE_LIST[I].MODE)
               FILE2TXT(FF,FILE_PATH,L)
               FILE2TXT(FF,FILE_NAME,L)
               SAMPLE_LIST[I].PATH = FindPath(FILE_PATH,FILE_NAME,"\AUDIO")
               IF SAMPLE_LIST[I].PATH = "" THEN
                    SAMPLE_LIST[I].PATH    = FILE_PATH
                    SAMPLE_LIST[I].OFFLINE = TRUE
               END IF
               SAMPLE_LIST[I].FILENAME = FILE_NAME
          NEXT I
     END IF

     'FILTERS
     R = GET(FF,,NB_FILTERS)
     IF NB_FILTERS > 0 THEN
          FILTER_LIST = NEW SAMPLE_LIST_ITEM[NB_FILTERS]
          FOR I = 0 TO NB_FILTERS - 1
               R = GET(FF,,FILTER_LIST[I].MODE)
               FILE2TXT(FF,FILE_PATH,L)
               FILE2TXT(FF,FILE_NAME,L)
               FILTER_LIST[I].PATH = FindPath(FILE_PATH,FILE_NAME,"\FILTERS")
               IF FILTER_LIST[I].PATH = "" THEN
                    FILTER_LIST[I].PATH    = FILE_PATH
                    FILTER_LIST[I].OFFLINE = TRUE
               END IF      
               FILTER_LIST[I].FILENAME = FILE_NAME
          NEXT I
     END IF

     'MELODIES
     R = GET(FF,,NB_MELODIES)
     IF NB_MELODIES > 0 THEN
          MELODY_LIST = NEW SAMPLE_LIST_ITEM[NB_MELODIES]
          FOR I = 0 TO NB_MELODIES - 1
               FILE2TXT(FF,FILE_PATH,L)
               FILE2TXT(FF,FILE_NAME,L)
               MELODY_LIST[I].PATH     = FindPath(FILE_PATH,FILE_NAME,"\MELODIES")
               IF MELODY_LIST[I].PATH = "" THEN
                    MELODY_LIST[I].PATH = FILE_PATH
                    MELODY_LIST[I].OFFLINE = TRUE
               END IF  
               MELODY_LIST[I].FILENAME = FILE_NAME
          NEXT I
     END IF

     'LOAD BOARDS
     R = GET(FF,,NB_BOARDS)
     FOR I = 0 TO NB_BOARDS - 1
          J = IIF(I > SOUNDRA.NB_KEYBOARDS_MAX-1,SOUNDRA.NB_KEYBOARDS_MAX-1,I)
          R = GET(FF,,*SOUNDRA.BB[J])
          SOUNDRA.BB[J]->SELF_CHECK()
     NEXT I
     
     Controls[ControlAccess[CS_BB_MODE]]->text.si     = SOUNDRA.BB[SOUNDRA.BB_NUM]->KEY_MODE
     Controls[ControlAccess[CS_BB_MODE]]->state.POWER = _
                (SOUNDRA.BB[SOUNDRA.BB_NUM]->KEY_MODE = SOUNDRA.KEY_MODES.ISLAND)
     Controls[ControlAccess[CS_BB_MODE]]->UPDATE_GDATA(TRUE)
     
     Controls[ControlAccess[CS_BB_LOCK]]->state.POWER = SOUNDRA.BB[SOUNDRA.BB_NUM]->LOCK_MODE
     Controls[ControlAccess[CS_BB_LOCK]]->UPDATE_GSTATE()
     
     Controls[ControlAccess[CS_BB]]->UPDATE_GTEXT(TRUE)
     
     FOR I = 0 TO NB_BOARDS - 1
          J = IIF(I > SOUNDRA.NB_KEYBOARDS_MAX-1,SOUNDRA.NB_KEYBOARDS_MAX-1,I)
          R = GET(FF,,*SOUNDRA.KB[J])
          SOUNDRA.KB[J]->SELF_CHECK()
     NEXT I
     I = SOUNDRA.KB[SOUNDRA.KB_NUM]->K_OFFSET.KEY - SOUNDRA.OCTAVE_MIN*TUNINGS(TUNING_CURRENT).NB_KEYS
     
     Controls[ControlAccess[CS_KB_POS]]->text.si      = I
     Controls[ControlAccess[CS_KB_POS]]->UPDATE_GTEXT(TRUE)
     
     Controls[ControlAccess[CS_KB_MODE]]->text.si     = SOUNDRA.KB[SOUNDRA.KB_NUM]->KEY_MODE
     Controls[ControlAccess[CS_KB_MODE]]->state.POWER = _
                (SOUNDRA.KB[SOUNDRA.KB_NUM]->KEY_MODE = SOUNDRA.KEY_MODES.ISLAND)
     Controls[ControlAccess[CS_KB_MODE]]->UPDATE_GDATA(TRUE)
     
     Controls[ControlAccess[CS_KB_LOCK]]->state.POWER = SOUNDRA.KB[SOUNDRA.KB_NUM]->LOCK_MODE
     Controls[ControlAccess[CS_KB_LOCK]]->UPDATE_GSTATE()
     
     Controls[ControlAccess[CS_KB]]->UPDATE_GTEXT(TRUE)

     'LOAD MODULE DATA
     R = GET(FF,,ROOT_MOD_ID)
     R = GET(FF,,NB_MODS)
     XP_MOD = NEW SOUNDRA.MODULE_XP PTR[NB_MODS]
     FOR I = 0 TO NB_MODS-1
          XP_MOD[I] = NEW SOUNDRA.MODULE_XP
          R = GET(FF,,*XP_MOD[I])
          IF R <> 0 THEN 
               '¤¤¤#183: "Error reading module #{$}:"
               TMPSTR = LNG_SET_EXT(183,"Error reading module #{$}:")
               TMPSTR  = TXTINSERT(TMPSTR,STR(I))
               TMPSTR &= ERR2TXT(R)
               TERMINAL(TMPSTR,MSG_ERROR)
               DELETE XP_MOD[I]
               XP_MOD[I] = 0
          ELSE 'CHECK IF MODULE IS NEWER THAN CURRENT VERSION BY VERIFYING ITS FUNCTION INDEX
               IF XP_MOD[I]->FCT_ID > SOUNDRA.CATALOG_SIZE-1 THEN
                    IF XP_MOD[I]->TYPE_ID > 0 THEN 'NOT A MASTER PRESET
                         CLOSE #FF
                         RETURN_CODE = -_IFF
                         GOTO LS_CLEAN
                    END IF
                    ISNEWER = TRUE
                    DELETE XP_MOD[I]
                    XP_MOD[I] = 0
               END IF
          END IF
     NEXT I
     IF ISNEWER = TRUE THEN
          '¤¤¤#184: "Some masters presets can't be loaded because they don't exist in this version."
   TMPSTR = LNG_SET_EXT(184,"Some masters presets can't be loaded because they don't exist in this version.")
          TERMINAL(TMPSTR,MSG_WARNING)
     END IF

     '### VERSION 1 SUPPORT
     IF     VERSION = "1.0" THEN
          R = SOUNDRA.LOAD_MIDI_MAPS10(FF) 'ONLY 1 CHANNEL
     ELSEIF VERSION = "1.1" THEN
          R = SOUNDRA.LOAD_MIDI_MAPS11(FF) '16 CHANNELS
     END IF

     CLOSE #FF

     'PROCESS DATA ------------------------------------------------------------------------------------
     
     'NAME
     SESSION_NAME = SESSION_FILE
     J = INSTRREV(SESSION_NAME,".")
     IF J THEN SESSION_NAME = MID(SESSION_NAME,1,J-1)

     'UPDATE TUNING
     MUTEXLOCK MT_TUNINGS(TUNING_CURRENT)
     MUTEXLOCK MT_TUNINGS(TUNING_COPY)
     IF ISTUNINGLOADED = TRUE THEN
          TUNINGS(TUNING_CURRENT) = TUNINGS(TUNING_COPY)
     ELSE
          TUNINGS(TUNING_COPY)    = TUNINGS(TUNING_CURRENT)
     END IF
     MUTEXUNLOCK MT_TUNINGS(TUNING_COPY)
     MUTEXUNLOCK MT_TUNINGS(TUNING_CURRENT)
     UpdateTuningControls()
     SOUNDRA.SETUP_MOD_TUNING()

     'DELETE FILES IN RAM
          'SAMPLES
     MUTEXLOCK   LI_WF_Sample.MUTEX
     FOR I = 0 TO LI_WF_Sample.LENGTH-1
          DELETE WF_Sample[I]
     NEXT I
     LI_WF_Sample.LENGTH = 0
     MUTEXUNLOCK LI_WF_Sample.MUTEX
     
          'FILTERS
     MUTEXLOCK   LI_WF_Filter.MUTEX
     FOR I = 0 TO LI_WF_Filter.LENGTH-1
          DELETE WF_Filter[I]
     NEXT I
     LI_WF_Filter.LENGTH = 0
     MUTEXUNLOCK LI_WF_Filter.MUTEX
     
          'MELODIES
     MUTEXLOCK   LI_WF_Melody.MUTEX
     FOR I = 0 TO LI_WF_Melody.LENGTH-1
          DELETE WF_Melody[I]
     NEXT I
     LI_WF_Melody.LENGTH = 0
     MUTEXUNLOCK LI_WF_Melody.MUTEX

     'IMPORT FILES
          'SAMPLES
     FOR I = 0 TO NB_SAMPLES-1
          ID = -1
          IF SAMPLE_LIST[I].OFFLINE = FALSE THEN
               'FIND IN LIBRARY
               ID = FIND_IN_WF_LIB(SAMPLE_LIST[I].PATH, SAMPLE_LIST[I].FILENAME, FALSE)
               'LOAD IN LIBRARY IF NOT FOUND
               IF ID = -1 THEN ID = LoadSample(SAMPLE_LIST[I].PATH, SAMPLE_LIST[I].FILENAME)
               'LOAD IN MEMORY ON SUCCESS
               IF ID > -1 THEN ID = LoadSample(WF_Library[ID],SAMPLE_LIST[I].MODE)
               'LOAD OFFLINE ON FAILURE
               IF ID <  0 THEN 
                    '¤¤¤#185: "Error loading sample {$}: "
                    TMPSTR = LNG_SET_EXT(185,"Error loading sample {$}: ")
                    TMPSTR  = TXTINSERT(TMPSTR,SAMPLE_LIST[I].FILENAME)
                    TMPSTR &= ERR2TXT(ID)
                    TERMINAL(TMPSTR,MSG_ERROR)
                    SAMPLE_LIST[I].OFFLINE = TRUE
               END IF
          END IF
          IF SAMPLE_LIST[I].OFFLINE THEN _
             ID = MakeOfflineWF(WF_Sample,LI_WF_Sample,SAMPLE_LIST[I].PATH,SAMPLE_LIST[I].FILENAME)
          'COPY WF_PTR ON SUCCESS
          IF ID > -1 THEN 
               SAMPLE_LIST[I].WF_PTR = WF_Sample[ID]
          ELSE
               '¤¤¤#186: "Error loading sample {$}: "
               TMPSTR = LNG_SET_EXT(186,"Error loading sample {$}: ")
               TMPSTR  = TXTINSERT(TMPSTR,SAMPLE_LIST[I].FILENAME)
               TMPSTR &= ERR2TXT(ID)
               TERMINAL(TMPSTR,MSG_ERROR)
          END IF
     NEXT I

          'FILTERS
     FOR I = 0 TO NB_FILTERS-1
          ID = -1
          IF FILTER_LIST[I].OFFLINE = FALSE THEN
               'LOAD IN MEMORY
               ID = LoadFilter(FILTER_LIST[I].PATH, FILTER_LIST[I].FILENAME)
               'LOAD OFFLINE ON FAILURE
               IF ID < 0 THEN
                    '¤¤¤#187: "Error loading filter {$}: "
                    TMPSTR = LNG_SET_EXT(187,"Error loading filter {$}: ")
                    TMPSTR  = TXTINSERT(TMPSTR,FILTER_LIST[I].FILENAME)
                    TMPSTR &= ERR2TXT(ID)
                    TERMINAL(TMPSTR,MSG_ERROR)
                    FILTER_LIST[I].OFFLINE = TRUE
               END IF
          END IF
          IF FILTER_LIST[I].OFFLINE = TRUE THEN _
             ID = MakeOfflineWF(WF_Filter,LI_WF_Filter,FILTER_LIST[I].PATH,FILTER_LIST[I].FILENAME)
          'COPY WF_PTR ON SUCCESS
          IF ID > -1 THEN
               FILTER_LIST[I].WF_PTR = WF_Filter[ID]
          ELSE
               '¤¤¤#188: "Error loading filter {$}: "
               TMPSTR = LNG_SET_EXT(188,"Error loading filter {$}: ")
               TMPSTR  = TXTINSERT(TMPSTR,FILTER_LIST[I].FILENAME)
               TMPSTR &= ERR2TXT(ID)
               TERMINAL(TMPSTR,MSG_ERROR)
          END IF
     NEXT I

          'MELODIES
     FOR I = 0 TO NB_MELODIES-1
          ID = -1
          IF MELODY_LIST[I].OFFLINE = FALSE THEN
               'LOAD IN MEMORY
               ID = LoadMelody(MELODY_LIST[I].PATH, MELODY_LIST[I].FILENAME)
               'LOAD OFFLINE ON FAILURE
               IF ID < 0 THEN
                    '¤¤¤#189: "Error loading melody {$}: "
                    TMPSTR = LNG_SET_EXT(189,"Error loading melody {$}: ")
                    TMPSTR  = TXTINSERT(TMPSTR,MELODY_LIST[I].FILENAME)
                    TMPSTR &= ERR2TXT(ID)
                    TERMINAL(TMPSTR,MSG_ERROR)
                    MELODY_LIST[I].OFFLINE = TRUE
               END IF
          END IF
          IF MELODY_LIST[I].OFFLINE THEN _
             ID = MakeOfflineWF(WF_Melody,LI_WF_Melody,MELODY_LIST[I].PATH,MELODY_LIST[I].FILENAME)
          'COPY WF_PTR ON SUCCESS
          IF ID > -1 THEN
              MELODY_LIST[I].WF_PTR = WF_Melody[ID]
          ELSE
               '¤¤¤#190: "Error loading melody {$}: "
               TMPSTR = LNG_SET_EXT(190,"Error loading melody {$}: ")
               TMPSTR  = TXTINSERT(TMPSTR,MELODY_LIST[I].FILENAME)
               TMPSTR &= ERR2TXT(ID)
               TERMINAL(TMPSTR,MSG_ERROR)
          END IF
     NEXT I

     'RESET SYNTH
     Controls[ControlAccess[CS_SYNTH_OUT]]->state.POWER = FALSE
     Controls[ControlAccess[CS_SYNTH_OUT]]->UPDATE_GSTATE()
     SOUNDRA.DELETE_ALL_MODULES() 'LOCKS
     Controls[ControlAccess[CS_MODULE_LIST]+MOD_LIBRARY]->UPDATE_GTEXT(TRUE)
     
     'CREATE ALL MODULES
     NB_MASTERS = 0
     FOR I = 0 TO NB_MODS - 1
          IF XP_MOD[I] > 0 THEN
               IF XP_MOD[I]->TYPE_ID = 0 THEN
                    NB_MASTERS += 1
                    SOUNDRA.IMPORT_MASTER_DATA(XP_MOD[I],DO_PATCH)
               ELSE
                    R = SOUNDRA.CREATE_MODULE(SOUNDRA.MODULES[I], XP_MOD[I]->FCT_ID, I) 'LOCKS
                    IF R THEN 
                         '¤¤¤#191: "Error creating module: "
                         TMPSTR = LNG_SET_EXT(191,"Error creating module: ")
                         TMPSTR &= ERR2TXT(R)
                         TERMINAL(TMPSTR,MSG_ERROR)
                    END IF
               END IF
          END IF
     NEXT I
     NB_MODS -= NB_MASTERS

     'RESET PLANE
     ID = ControlAccess[CS_SYNTH_OUT]
     Controls[ID]->X = Controls[ID]->X - SOUNDRA.PLANE_X
     Controls[ID]->Y = Controls[ID]->Y - SOUNDRA.PLANE_Y
     SOUNDRA.PLANE_X = 0
     SOUNDRA.PLANE_Y = 0
     XSO = Controls[ID]->X
     YSO = Controls[ID]->Y
     ZR  = SETTINGS.ZOOM_FACTOR_CURRENT
     ctrl_update(ID)
     
     'SETUP MODULES FROM IMPORTED DATA
     FOR I = 0 TO NB_MODS - 1
          IF XP_MOD[I] THEN
               IF SOUNDRA.MODULES[I] THEN
                    'WAVE FILE
                    WF_PTR = 0
                    WF_ID  = XP_MOD[I]->WF_ID
                    IF WF_ID > -1 THEN
                         SELECT CASE XP_MOD[I]->FCT_ID
                         CASE SOUNDRA.MM_SAMPLE
                              IF WF_ID < LI_WF_Library.LENGTH THEN WF_PTR = SAMPLE_LIST[WF_ID].WF_PTR
                         CASE SOUNDRA.MM_CONVOLUTION
                              IF WF_ID < LI_WF_Filter.LENGTH  THEN WF_PTR = FILTER_LIST[WF_ID].WF_PTR
                         CASE SOUNDRA.MM_SEQUENCER
                              IF WF_ID < LI_WF_Melody.LENGTH  THEN WF_PTR = MELODY_LIST[WF_ID].WF_PTR
                         END SELECT
                    END IF
                    SOUNDRA.IMPORT_MODULE_DATA(XP_MOD[I],SOUNDRA.MODULES[I],NB_MODS,WF_PTR,DO_PATCH)
                    IF ROOT_MOD_ID = I THEN ROOT = SOUNDRA.MODULES[I]
                    XP_MOD[I]->X = ZR*(XP_MOD[I]->X - XSO) + XSO
                    XP_MOD[I]->Y = ZR*(XP_MOD[I]->Y - YSO) + YSO
                    R = CreateModControls(SOUNDRA.MODULES[I],XP_MOD[I]->X,XP_MOD[I]->Y)
                    IF R < 1 THEN
                         '¤¤¤#192: "Error creating module controls: "
                         TMPSTR = LNG_SET_EXT(192,"Error creating module controls: ")
                         TMPSTR &= ERR2TXT(R)
                         TERMINAL(TMPSTR,MSG_ERROR)
                    END IF
               END IF
          END IF
     NEXT I

     'MIDI SETUP
     MUTEXLOCK   SOUNDRA.MT_MIDICC
     SOUNDRA.SETUP_MIDI_MAPS(NB_MODS)
     MUTEXUNLOCK SOUNDRA.MT_MIDICC
     REFRESH_MIDI_LISTS()

     SOUNDRA.DEFRAG_MOD_LIST(NB_MODS) 'MUST REMAIN AFTER MIDI SETUP FOR MOD_IDs TO BE VALID

     'SYNTH SETUP
     SOUNDRA.CP_DATA.REALIGN   = TRUE
     Controls[ControlAccess[CS_SYNTH_OUT]]->state.POWER = (ROOT > 0)
     Controls[ControlAccess[CS_SYNTH_OUT]]->UPDATE_GSTATE()
     SOUNDRA.DO_PRECOMPILE(FALSE,FALSE,ROOT)

     'SESSION
     IF SESSION_PATH <> SETTINGS.SESSION_PATH OR SESSION_FILE <> SETTINGS.SESSION_FILE THEN
          SETTINGS.SESSION_PATH = SESSION_PATH
          SETTINGS.SESSION_FILE = SESSION_FILE
          SETTINGS.SESSION_NAME = SESSION_NAME
          SaveSettings()
     END IF
     I = ControlAccess[CS_SESSION_TAB0]
     Controls[I]->text.content = " " & SESSION_NAME
     Controls[I]->UPDATE_GTEXT()
     Controls[ControlAccess[CS_MODULE_LIST]+MOD_LIBRARY]->UPDATE_GTEXT(TRUE)

     'CLEAN -------------------------------------------------------------------------------------------
LS_CLEAN:
     IF SAMPLE_LIST THEN DELETE[] SAMPLE_LIST
     IF FILTER_LIST THEN DELETE[] FILTER_LIST
     IF MELODY_LIST THEN DELETE[] MELODY_LIST
     IF XP_MOD > 0 THEN 
          FOR I = 0 TO NB_MODS-1
               IF XP_MOD[I] > 0 THEN DELETE XP_MOD[I]
          NEXT I
          DELETE[] XP_MOD
     END IF
     FlushActionLog()
     SOUNDRA.REDRAW_CONNECTORS = TRUE

     RETURN RETURN_CODE
END FUNCTION

SUB BUNDLECOPY(DOCOPYBUNDLE AS I32)
     DIM AS I32 I
     DIM AS TXT TMPSTR, DEMO_FOLDER = "\SYNTHS\DEMO " & SOFTWARE_VERSION
     'COPY BUNDLED FILES TO LOCAL USER FOLDER
     TMPSTR = EXEPATH & DEMO_FOLDER
     Browse(@FOLDER0,TMPSTR,".soundra",FALSE,FALSE)
     IF (DOCOPYBUNDLE AND 1) = 1 THEN
          '¤¤¤#193: "Copying bundled synth files."
          TMPSTR = LNG_SET_EXT(193,"Copying bundled synth files.")
          TERMINAL(TMPSTR)
          FOR I = 0 TO FOLDER0.LI_ITEM.LENGTH-1
               IF NOT(FOLDER0.GetAttr(I) AND fbDirectory) THEN _ 
                    FILECOPY(FOLDER0.GetFullName(I),HOMEPATH & DEMO_FOLDER & "\" & FOLDER0.GetName(I))
          NEXT I
     END IF
     TMPSTR = EXEPATH & "\TUNINGS"
     Browse(@FOLDER0,TMPSTR,".tng",FALSE,FALSE)
     IF (DOCOPYBUNDLE AND 2) = 2 THEN
          '¤¤¤#194: "Copying bundled tuning files."
          TMPSTR = LNG_SET_EXT(194,"Copying bundled tuning files.")
          TERMINAL(TMPSTR)
          FOR I = 0 TO FOLDER0.LI_ITEM.LENGTH-1
               IF NOT(FOLDER0.GetAttr(I) AND fbDirectory) THEN _ 
                    FILECOPY(FOLDER0.GetFullName(I),HOMEPATH & "\TUNINGS\" & FOLDER0.GetName(I))
          NEXT I
     ELSE
          FOR I = 0 TO FOLDER0.LI_ITEM.LENGTH-1
               IF NOT(FOLDER0.GetAttr(I) AND fbDirectory) THEN _
                    IF NOT(FILEEXISTS(FOLDER0.GetFullName(I))) THEN _ 
                    FILECOPY(FOLDER0.GetFullName(I),HOMEPATH & "\TUNINGS\" & FOLDER0.GetName(I))
          NEXT I
     END IF
END SUB
'------------------------------------------------------------------------------------------------------------