Welcome to %s forums

BrainModular Users Forum

Login Register

Cannot get saveToFile() to work in script.

I need help on a Patch
Post Reply
woodslanding
Member
Posts: 1271

Cannot get saveToFile() to work in script.

Post by woodslanding » 25 Jan 2023, 05:26

The script saves and loads state for a set of controls into a text file, via object commands. It creates a list of tags, and a corresponding list of addresses for the controls in the patch.

In this script, the line just before the saveToFile() line gets printed, but the method stops there, and the file doesn't get written. I can't figure out why the method just stops executing. The call is in the SaveBank() method, which gets called from processIdle() after the flag 'gReadyToWrite' is set via the save input button in the callback. It's the third-to-last method, before callback() and processIdle().

Interestingly, if I reset the flag AFTER calling the saveBank() method, it saves continously!! It either doesn't save, or saves continously, I can't get it to happen just once. Also, if I skip the flag, and call it directly from callback instead of processIdle, it also doesn't save.

This is with the latest version (I updated because there was mention of a file issue in the release notes. But this happens the same way with both versions....)

I have several similar scripts that are reading and writing fine via the same methods, and I can't figure out what's different with this one. But this behaves the same way in an empty WKP, so it's something with the script.

Here is the whole script:

Code: Select all

const DBUG_ON = TRUE;

const CHAN = 'TEST';

const PRESET_FOLDER = 'PRESETS';

const TEXT_TAGS = ['details'];
const INT_TAGS = ['midi-in-ch','transpose','audio-in','cc-to-pitch','mw-midi','at-midi','params-on'];  //+ param nums + param channels, 8 buttons, 4 encoder button pairs, 8 encoders
const FLOAT_TAGS = ['input-trim','output-trim','hue','saturation'];

const TYP_STR = 0; const TYP_INT = 1; const TYP_FLT = 2;
//subpatches
const TAGS = ['param','text','param','step','param','range.maxpos','range.minpos'];
const SUBS = ['renaming','renaming','macros','macros','controls','controls','controls'];
const TYPES = [TYP_INT, TYP_STR, TYP_INT, TYP_INT, TYP_INT, TYP_FLT, TYP_FLT];
const POLYS = [6,6,4,4,4,4,4];
//added to address of subpatches above
const SUBPATCH = 'instparamctl.1.';
//added to all addresses
const PREFIX = 'patch.sidepanel.1.bankdata.1.';

const EXTENSION = '.bank';
const DTAG = 'bankManager_';
const DELIMITER = '=';
const DOT = '.';
const NADA = '';

var dataPathIN,recallIN,saveIN,channelIN,bankIN, instIN, bankLoadedOUT: tParameter;

var gDataString, gTags, gAddresses, gTypes: tStringlist;
var i,j: integer;
var gReadyToWrite, gReadyToLoad, gListsCreated : boolean;

procedure init;
var i: integer;
begin  
    SetModuleColor($808080+496999); 
    dataPathIN   := CreateParam('data path',ptTextField,pioInput); 
    recallIN   := CreateParam('recall',ptButton,pioInput);                 
    saveIN  := CreateParam('save',ptButton,pioInput); 
    instIN := CreateParam('inst name',ptTextField, pioInput);  
    bankIN := CreateParam('bank name',ptTextField, pioInput); 

    bankLoadedOUT := CreateParam('bank loaded', ptDataField, pioOutput);

    gDataString.create; 
    gTags.create; 
    gAddresses.create; 
    gTypes.create;
end;

procedure destroy;  begin gDataString.free; gTags.free; gAddresses.free; gTypes.free; end;

procedure debug(s: string); begin if DBUG_ON then strace(DTAG + CHAN + ': ' +s); end;

procedure iDebug(s: string; num : integer) begin debug(s + ' '  + intToStr(num)); end;

procedure tsDebug(ts: tStringlist; note: string); 
var i: integer;
begin     
    Debug(note); 
    for i := 0 to ts.count - 1 do debug(intToStr(i) + ': ' + ts.getStrings(i));
end;

function stringsMatch(string1:string;string2:string) : boolean;
begin
    //debug('matching strings:' + string1 + ', '+string2);
    result := (upperCase(trim(string1)) = uppercase(trim(string2)));   
end;

function getBankPath() : string; 
var path: string;
begin 
    path := dataPathIN.asString + instIN.asString + PathDelim() + bankIN.asString + EXTENSION;
    //debug('bank path = ' + path);
    result := path; 
end;

function getDataForLine(s: string) : string;
var data: string;
begin 
    data := copy(s,pos(Delimiter,s) + 1,length(s));
    //debug('got data for line: ' + data);
    result := data;
end; 

function getTagForLine(s: string) : string;
var tag: string;
begin 
    tag := copy(s, 1, pos(DELIMITER,s) - 1);
    //debug('got line: ' + s);
    //debug('got tag for line: ' + tag);
    result := tag;
end; 

function extractData(data : tStringlist; tag: string) : string;   
var line: string;
var i: integer;
begin  
    for i := 0 to data.count - 1 do
    begin
        if stringsMatch(tag, getTagForLine(data.getStrings(i))) then
            result := getDataForLine(data.getStrings(i));
    end;
end; 

procedure updateLists(tag: string; discard: string; subpatch: string; dtype: integer);
begin
    gTags.add(subpatch + tag);
    gAddresses.add(PREFIX + discard + subpatch + tag);
    gTypes.add(intToStr(dtype));
end;

procedure createLists();
var name, paramName: string;
paramNum: integer;
begin
    gTags.clear;
    gAddresses.clear;
    gTypes.clear;
    for i := 0 to (length(INT_TAGS) - 1)   do updateLists(INT_TAGS[i], NADA, NADA, TYP_INT);
    for i := 0 to (length(FLOAT_TAGS) - 1) do updateLists(FLOAT_TAGS[i], NADA, NADA, TYP_FLT);
    for i := 0 to (length(TEXT_TAGS) - 1)  do updateLists(TEXT_TAGS[i], NADA, NADA, TYP_STR);
    //for subpatches
    for i := 0 to (length(TAGS) - 1)   do
        for j := 1 to (POLYS[i]) do updateLists(TAGS[i],SUBPATCH, SUBS[i] + DOT + intToStr(j) + DOT, TYPES[i]);   
    //for i := 0 to (gAddresses.count - 1) do begin debug(gAddresses.getStrings(i)); debug(gTags.getStrings(i)); end;     
end;

procedure sendData(idx: integer; data: string);
begin
    //debug(gAddresses.getStrings(idx));
    //debug( data );
    if StrToInt(gTypes.getStrings(idx)) = TYP_INT then
        setObjectInteger(gAddresses.getStrings(idx), strToInt(data))
    else if strToInt(gTypes.getStrings(idx)) = TYP_FLT then
        setObjectFloat(gAddresses.getStrings(idx), strToFloat(data))
    else if strToInt(gTypes.getStrings(idx)) = TYP_STR then
        setObject(gAddresses.getStrings(idx), data);
end;

procedure recall();
begin
    gReadyToLoad := TRUE;
end

procedure saveBank();
var int: integer;
var fl: single;
var st: string;
begin
    debug('saving:' + getBankPath());
    gDataString.clear;
    for i := 0 to (gTags.count - 1) do
        if StrToInt(gTypes.getStrings(i)) = TYP_INT then
            begin
                int := getObjectInteger(gAddresses.getStrings(i));
                gDataString.add(gTags.getStrings(i) + DELIMITER + intToStr(int));
            end 
        else if strToInt(gTypes.getStrings(i)) = TYP_FLT then
            begin
                fl := getObjectFloat(gAddresses.getStrings(i));
                gDataString.add(gTags.getStrings(i) + DELIMITER + floatToStr(fl));
            end 
        else if strToInt(gTypes.getStrings(i)) = TYP_STR then
            begin
                st := getObject(gAddresses.getStrings(i));
                gDataString.add(gTags.getStrings(i) + DELIMITER + st);
            end 
    //tsDebug(gDataString, 'data: ');
    debug('string created');  //Prints out fine.
    gDataString.SaveToFile(getBankPath());   // This is a valid path, in my case, from trace:  [68790] bankManager_1: saving:D:\HH 5\RIG\PRESETS\Keyscape\Pianos.bank
    debug('file saved');  //THIS LINE IS NEVER REACHED
end

procedure Callback(n:integer);
begin
 iDebug('callback input: ',n);
    //set the lists up first thing
    if not gListsCreated then 
        begin 
            createLists; 
            gListsCreated := TRUE;
            debug('created lists'); 
            bankLoadedOUT.asInteger(1);  //we turn this off during loading, and back on when done
        end;
    CASE n of
        saveIN: if (saveIN.asInteger() > 0) then 
                gReadyToWrite := TRUE;  
        recallIN: if (recallIN.asInteger > 0) and FileExists(getBankPath()) then 
            begin
                gReadyToLoad := TRUE;
                bankLoadedOUT.asInteger(0); 
            end;
    end;
end;

Procedure processidle;
var i,j: integer;
var line, tag: string;    
BEGIN 
    if gReadyToWrite then
    begin                     
        gReadyToWrite := FALSE;   //if I put this AFTER saveBank() it saves the bank continuously!!!
        saveBank();                                  
    end;
    if gReadyToLoad then
    begin       
        debug('recalling: ' + getBankPath());
        gDataString.clear;
        gDataString.loadfromFile(getBankPath());
        for i := 0 to (gDataString.count - 1) do
        begin
            line := gDataString.getStrings(i);
            //debug('line = ' + line);
            //check each tag in our list to match with the dataStringIn line
            for j := 0 to (gTags.count - 1) do
            begin
                tag := gTags.getStrings(j);
                //debug('tag found: ' + tag);
                if stringsMatch(tag,getTagForLine(line)) then 
                    sendData(j, getDataForLine(line));
            end;
        end;
        gReadyToLoad := FALSE;
        bankLoadedOUT.asInteger(1);
    end
END
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

User avatar
senso
Site Admin
Posts: 4371
Location: France
Contact:

Post by senso » 25 Jan 2023, 08:14

are you serious :-) ???
your script is too complex for me !
The only thing I can tell is that the following script is working as expected.
script-save.wkp
so the bug is somewhere else...
You do not have the required permissions to view the files attached to this post.

woodslanding
Member
Posts: 1271

Post by woodslanding » 25 Jan 2023, 17:40

yeah, I know. But I thought there might be something obvious in there to someone more experienced than I. And I wasn't particularly expecting to hear from the developer...

I've gotten help with far more complex scripts than this on the Reaper forum, but of course never ever from the development team ;)
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

woodslanding
Member
Posts: 1271

Post by woodslanding » 25 Jan 2023, 18:47

Okay, I will try to simplify when I get a chance, but the following script works fine in usine 221115 and does not work in the two most recent Usine versions.

EDIT: never mind, there is something else going on. This does work in an empty wkp in the newest version.

Code: Select all


const DBUG_ON = TRUE;

const CHAN = '1';

const PRESET_FOLDER = 'PRESETS';

const TEXT_TAGS = ['details'];
const INT_TAGS = ['midi-in-ch','transpose','audio-in','cc-to-pitch','mw-midi','at-midi','params-on'];  //+ param nums + param channels, 8 buttons, 4 encoder button pairs, 8 encoders
const FLOAT_TAGS = ['input-trim','output-trim','hue','saturation'];

const TYP_STR = 0; const TYP_INT = 1; const TYP_FLT = 2;
//subpatches
const TAGS = ['param','text','param','step','param','range.maxpos','range.minpos'];
const SUBS = ['renaming','renaming','macros','macros','controls','controls','controls'];
const TYPES = [TYP_INT, TYP_STR, TYP_INT, TYP_INT, TYP_INT, TYP_FLT, TYP_FLT];
const POLYS = [6,6,4,4,4,4,4];
//added to address of subpatches above
const SUBPATCH = 'instparamctl.1.';
//added to all addresses
const PREFIX = 'patch.sidepanel.1.bankdata.1.';

const EXTENSION = '.bank';
const DTAG = 'bankManager_';
const DELIMITER = '=';
const DOT = '.';
const NADA = '';

var dataPathIN,recallIN,saveIN,channelIN,bankIN, instIN, bankLoadedOUT: tParameter;

var gDataString, gTags, gAddresses, gTypes: tStringlist;
var i,j: integer;
var gReadyToWrite, gReadyToLoad, gListsCreated : boolean;

procedure init;
var i: integer;
begin  
    SetModuleColor($808080+496999); 
    dataPathIN   := CreateParam('data path',ptTextField,pioInput); 
    recallIN   := CreateParam('recall',ptButton,pioInput);                 
    saveIN  := CreateParam('save',ptButton,pioInput); 
    instIN := CreateParam('inst name',ptTextField, pioInput);  
    bankIN := CreateParam('bank name',ptTextField, pioInput); 

    bankLoadedOUT := CreateParam('bank loaded', ptDataField, pioOutput);
    bankLoadedOUT.asInteger(1);  //we turn this off during loading, and back on when done

    gDataString.create; 
    gTags.create; 
    gAddresses.create; 
    gTypes.create;
end;

procedure destroy;  begin gDataString.free; gTags.free; gAddresses.free; gTypes.free; end;

procedure debug(s: string); begin if DBUG_ON then strace(DTAG + CHAN + ': ' +s); end;

procedure iDebug(s: string; num : integer) begin debug(s + ' '  + intToStr(num)); end;

procedure tsDebug(ts: tStringlist; note: string); 
var i: integer;
begin     
    Debug(note); 
    for i := 0 to ts.count - 1 do debug(intToStr(i) + ': ' + ts.getStrings(i));
end;

function stringsMatch(string1:string;string2:string) : boolean;
begin
    //debug('matching strings:' + string1 + ', '+string2);
    result := (upperCase(trim(string1)) = uppercase(trim(string2)));   
end;

function getBankPath() : string; 
var path: string;
begin 
    path := dataPathIN.asString + instIN.asString + PathDelim() + bankIN.asString + EXTENSION;
    debug('bank path = ' + path);
    result := path; 
end;

function getDataForLine(s: string) : string;
var data: string;
begin 
    data := copy(s,pos(Delimiter,s) + 1,length(s));
    //debug('got data for line: ' + data);
    result := data;
end; 

function getTagForLine(s: string) : string;
var tag: string;
begin 
    tag := copy(s, 1, pos(DELIMITER,s) - 1);
    //debug('got line: ' + s);
    //debug('got tag for line: ' + tag);
    result := tag;
end; 

function extractData(data : tStringlist; tag: string) : string;   
var line: string;
var i: integer;
begin  
    for i := 0 to data.count - 1 do
    begin
        if stringsMatch(tag, getTagForLine(data.getStrings(i))) then
            result := getDataForLine(data.getStrings(i));
    end;
end; 

procedure updateLists(tag: string; discard: string; subpatch: string; dtype: integer);
begin
    gTags.add(subpatch + tag);
    gAddresses.add(PREFIX + discard + subpatch + tag);
    gTypes.add(intToStr(dtype));
end;

procedure createLists();
var name, paramName: string;
paramNum: integer;
begin
    gTags.clear;
    gAddresses.clear;
    gTypes.clear;
    for i := 0 to (length(INT_TAGS) - 1)   do updateLists(INT_TAGS[i], NADA, NADA, TYP_INT);
    for i := 0 to (length(FLOAT_TAGS) - 1) do updateLists(FLOAT_TAGS[i], NADA, NADA, TYP_FLT);
    for i := 0 to (length(TEXT_TAGS) - 1)  do updateLists(TEXT_TAGS[i], NADA, NADA, TYP_STR);
    //for subpatches
    for i := 0 to (length(TAGS) - 1)   do
        for j := 1 to (POLYS[i]) do updateLists(TAGS[i],SUBPATCH, SUBS[i] + DOT + intToStr(j) + DOT, TYPES[i]);   
    //for i := 0 to (gAddresses.count - 1) do begin debug(gAddresses.getStrings(i)); debug(gTags.getStrings(i)); end;     
end;

procedure sendData(idx: integer; data: string);
begin
    //debug(gAddresses.getStrings(idx));
    //debug( data );
    if StrToInt(gTypes.getStrings(idx)) = TYP_INT then
        setObjectInteger(gAddresses.getStrings(idx), strToInt(data))
    else if strToInt(gTypes.getStrings(idx)) = TYP_FLT then
        setObjectFloat(gAddresses.getStrings(idx), strToFloat(data))
    else if strToInt(gTypes.getStrings(idx)) = TYP_STR then
        setObject(gAddresses.getStrings(idx), data);
end;

procedure recall();
begin
    gReadyToLoad := TRUE;
end

procedure initSave();
var int: integer;
var fl: single;
var st: string;
begin
    gDataString.clear;
    for i := 0 to (gTags.count - 1) do
        if StrToInt(gTypes.getStrings(i)) = TYP_INT then
            begin
                int := getObjectInteger(gAddresses.getStrings(i));
                gDataString.add(gTags.getStrings(i) + DELIMITER + intToStr(int));
            end 
        else if strToInt(gTypes.getStrings(i)) = TYP_FLT then
            begin
                fl := getObjectFloat(gAddresses.getStrings(i));
                gDataString.add(gTags.getStrings(i) + DELIMITER + floatToStr(fl));
            end 
        else if strToInt(gTypes.getStrings(i)) = TYP_STR then
            begin
                st := getObject(gAddresses.getStrings(i));
                //st := VarToStr(v);
                gDataString.add(gTags.getStrings(i) + DELIMITER + st);
            end 
    
end

procedure Callback(n:integer);
begin
 iDebug('callback input: ',n);
    //set the lists up first thing
    if not gListsCreated then 
        begin 
            createLists; 
            gListsCreated := FALSE;
            debug('created lists'); 
        end;
    CASE n of
        saveIN: if saveIN.asInteger > 0 then gReadyToWrite := TRUE;
        recallIN: if (recallIN.asInteger > 0)then //and FileExists(getBankPath()) then   //not supported in 221115
        begin
            gReadyToLoad := TRUE;
            bankLoadedOUT.asInteger(0); 
        end;
    end;
end;

Procedure processidle;
var i,j: integer;
var line, tag: string;    
BEGIN 
    if gReadyToWrite then
    begin  
        initSave();                           
        debug('saving:' + getBankPath());
        tsDebug(gDataString, 'data: ');
        gDataString.SaveToFile(getBankPath());                
        gReadyToWrite := FALSE;        
    end;
    if gReadyToLoad then
    begin       
        debug('recalling: ' + getBankPath());
        gDataString.clear;
        gDataString.loadfromFile(getBankPath());
        for i := 0 to (gDataString.count - 1) do
        begin
            line := gDataString.getStrings(i);
            //debug('line = ' + line);
            //check each tag in our list to match with the dataStringIn line
            for j := 0 to (gTags.count - 1) do
            begin
                tag := gTags.getStrings(j);
                //debug('tag found: ' + tag);
                if stringsMatch(tag,getTagForLine(line)) then 
                    sendData(j, getDataForLine(line));
            end;
        end;
        gReadyToLoad := FALSE;
        bankLoadedOUT.asInteger(1);
    end
END
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

woodslanding
Member
Posts: 1271

Post by woodslanding » 25 Jan 2023, 19:08

It only works when the instIN is an empty string! Getting there...
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

woodslanding
Member
Posts: 1271

Post by woodslanding » 25 Jan 2023, 20:47

Okay, here is a test wkp that highlights what is happening. All I have to do is add a subfolder to the path in, and the script fails. In this patch, you can press the save button and it saves. Change the selector from 0 to 1, and it adds a subfolder to the path. Now press save. The patch does not save, and the trace goes into an endless loop. EDIT: updated a MUCH simpler script that fails without looping, by moving the flag location.

If you change the selector back, the loop stops. But the file never gets saved....

EDIT: in this version, it does not loop, but it still fails to write, even when the correct folder exists.

I cannot get any version of this script to malfunction under v.221115.

I will try to make a simplified version that malfunctions the same way, but I am out of time for today.
EDIT: vastly simplified version attached.

But maybe someone else can verify it does the same thing on their system?
You do not have the required permissions to view the files attached to this post.
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

User avatar
senso
Site Admin
Posts: 4371
Location: France
Contact:

Post by senso » 26 Jan 2023, 08:19

sorry to tell you that the script works as expected on my machine.
so, things are always as simple...

woodslanding
Member
Posts: 1271

Post by woodslanding » 28 Jan 2023, 02:13

It is working on my machine in the newest version, thanks!

Both saveToFile() and loadFromFile() still don't work when I put them in processIdle() though.... and excecution stops at that line, so if I put a flag after the command it is never reached, and I get an endless loop.

I'm hoping these text files are small enough and read/write quickly enough not to cause dropouts when run directly in callback().... they work fine when called from there.
Custom Ryzen 5900x MATX build, Win10, Fireface UFX, touchscreen
Custom 2 manual midi keyboard
Usine, Kontakt, Reaktor, Synthmaster, Byome, Arturia, Soundtoys, Unify

Post Reply