====== Mozaic: Include Snippet - Migration Manager ====== {{tag>Mozaic midi_scripting}} The Migration Manager (Include) is a code snippet that allows to migrate script parameters between different script versions. You just need to add 2 event calls and define 4 events for callbacks. ==== Problem Solved by the Snippet ==== One can easily state-safe a script and reload its full settings including the scripts source into another Mozaic instance - but if you load a newer version of the script or minimaly change the source and do an 'upload', all your precious settings are lost. The Migration Manager allows to recover all settings from another instance. ==== Migration Instructions for 'End Users' ==== * Before loading a new script version, save the to-be-preseved configuration to a new preset name (either in the host or in Mozaic) * Update the script to the new version by loading the script - this will come up with the scripts default settings * Turn the IMPORT knob of the script * Open a second Moazic instance and load the saved configuration, you have ten seconds to do this * The new version will pick up the configuration and you are ready to go ==== Use-Case for Script-Devs ==== If you are a script dev, you can use this feature to fast-restre your settings after you minimally changed code and hit 'UPLOAD'. Just keep the 'sender' instance open and reload it when importing to the receiver. ==== Suitable Scripts ==== * Your script should have many parmeters, more than you would like to dial in manually either when updating or for every test during development the script * Updating of the script should be intended or likely ==== Scripts using the Migration Manager Snippet ==== * The [[https://patchstorage.com/migration-manager-include/|Migration Manager (Include) Demo]] * The [[https://patchstorage.com/midi-multicast/|MIDI MultiCast]] script transfers its up to 7x16 routing scenes and config variables * The[[https://patchstorage.com/mutator/|MutatoR]] script transfers around 12KB of data in 13 migration steps * The[[https://patchstorage.com/ms6-sysex-performance-editor-and-spatial-processor/|MS6 Performance Editor and Spatial Processor]] script transfers its settings in 2 migrations steps \\ ===== Adding the 'Migration Manager' to own scripts ==== You can either grab the snippet from one of the example scripts where you find the to-be-included snippet below the line >>>>>>>> MIGRATION MANAGER INCLUDE <<<<<<<< at the end of the example - check that you found the current v1.2 snippet code. Or you copy the code from the large code block at the end of this page to the end of your own script. * In the @OnLoad, right at the start call @MigrationManagerInit and at the end setup your unique script's id and call @MigrationManagerOnLoad. * Add an 'IMPORT' knob in your scripts GUI and call @MigrationManagerOnImport to start importing if the knob is turned. * Your scipt also needs to define the 4 event functions @MigrateSend, @MigrateRead, @MigrateImportDone,@MigrateExportDone that will be called by the Migration Manager. * Choose a unique **mmScriptId** for your script, see [[#Reserved mmScriptId‘s]] ==== @OnLoad ==== Right at the start of this event function add Call @MigrationManagerInit to initialize all variables. At the end of your @OnLoad, setup you scripts unique id and Call @MigrationManagerOnLoad for the optinal sending of data There are three types of scripts - ) Your script doesn't define an own OnTimer - ) Your script defines an own OnTimer, which is already running at this point and which has an intervall less than 200 - ) Your script defines an own OnTimer, but its either with a slower interval (>200) or it only runs conditional Only scripts of type 3) need to add the IF block after the Call @MigrationManagerOnLoad that temporary sets up a faster timer timer interval and start the timer if upon this 'upload' another instance is waiting for a transmission. \\ After migration is finished (takes several timer events), the @MigrateExportDone event is called, where you can undo these temporary changes mmScriptId = 0xDEAA0 // Please choose an new unique number up to 24bits ! mmScriptTimer = YES // YES if script defines an own timer. Remove the line or // set to NO if Migration Manager should manage the timer Call @MigrationManagerOnLoad if mmIsMigrating // This code is of type 3) (own slower timer, and not yet running) SetTimerInterval 50 StartTimer endif ==== Choose a unique mmScriptId ==== Each script needs to use an own unique mmScriptId. There could be several totally different scripts including the Migration Manager Snippet loaded in a single AUv3 host session, therefore the scripts unique id is used to identify other instances of this specific script and to ensure the scripts ‚own‘ data is received. The id can be understood as „sender/receiver name or address“ of a script, the Migration Manager only connects scripts with identical mmScriptId. A later change of the id for a script already in use is not advisable as the data of older saved instances can no longer be imported. The maximum value for mmScriptId is 24bits or 6 hex chars (0x000100 to 0xFFFF80) \\ === Reserved mmScriptId‘s === The following mmScriptId ranges are already in use by other scripts and should be avoided: ^ ^ mmScriptId ^ end of range ^ script ^ | | 0x4200 | 0x420F | MS6 SysEx Performance Editor and Spatial Processor | | | | 0xDEAA0 | 0xDEAAF | Migration Manager Demo | | | | 0x4D4D43 | 0x4D4D4F | Midi Multicast | | | | 0x50C001 | 0x50C01F | MutatoR | | ==== @OnTimer ==== When your script uses an own timer like in type b) or c), make sure to wrap your original OnTimer code into an IF block like shown below. During both migration directions (send of read part) the timer needs to be running and calling the Migration Manger @OnTimer if mmIsMigrating Call @MigrationManagerOnTimer else // Code you already had in your timer endif @End ==== @OnKnobChange ==== If the knob ideally labeled 'IMPORT' is turned, you can add the following code to initiate the receiving of data and additionally support double-tap _knob = LastKnob _val = GetKnobValue _knob if _knob = 1 ... elseif _knob = 2 and _val >= 64 // IMPORT knob with double-tap feature SetKnobValue 2, 127 // Turn knob to 'on' position Call @MigrationManagerOnImport // Call the import manager // For a script of type 3) you need to temporary modify the timer interval and start it SetTimerInterval 50 StartTimer endif \\ ==== Migration Manager Callbacks Events ==== The transmission happens in the two user events @MigrateSend and @MigrateRead by copying to/from the global98 array, one set of variables on each call. Internally the MigrationManager uses the global97 array to drive the communication, calling your event functions several times until all data is transmitted. ==== @MigrateSend ==== Input: pIdx \\ Output: pType, global98 The pIdx identifies the parameter to be transfered, the index starts with your unique script-id and is incremented on every transmission step. You code needs to fill in global98 and copy pIdx into pType - to indicate the end of transmission, set pType to MIGRATION_DONE @MigrateSend // params: pIdx output: pType, global98 pType = pIdx // Transfer preset data one by one if pIdx = mmScriptId CopyArray data,global98,16 // First transfer 16 entries of 'data' elseif pType = mmScriptId +1 CopyArray things,global98,16 // Then transfer 16 entries of 'things' elseif pType = mmScriptId +2 CopyArray stuff,global98,16 // On third call transfer 16 entries of 'stuff' elseif pType = mmScriptId +3 global98[0] = color_code // On fourth call transfer single entry for colorCode else pType = MIGRATION_DONE // The last call signals end of transmission endif @End ==== @MigrateRead ==== Input: pType, global98x The MigrationRead event function is called after the sender updated the data supplying a pType parameter specifiying the content of global98 array. The MIGRATION_DONE is not forwarded to this event, but instead @MigrateImportDone is called @MigrateRead // params: pType, global98 if pType = mmScriptId // Receive is analogous CopyArray global98,data,16 // First call copy into 16 'data' entries elseif pType = mmScriptId +1 CopyArray global98,things,16 // Second call copy into 16 'things' entries elseif pType = mmScriptId +2 CopyArray global98,stuff, 16 // Third call copy into 16 'stuff' entries elseif pType = mmScriptId +3 color_code = global98[0] // Last call restores the colorCode entry endif @End If you need to transfer more variables, just add them accordingly to both MigrateRead and MigrateSend, using the same offset in both events. For version updates, only add new variables at the end of the IF-cascade and take care of filling in default values for variables that an older script version might not supply. ==== @MigrateImportDone ==== At the end of an importing transmission started by a call to @MigrationManagerOnImport, the @MigrateImportDone event is called by the Migration Manager. If your script is of type 3), then this is the place to undo the temporary timer settings @MigrateImportDone // param pType (either MIGRATION_DONE or MIGRATION_ERROR) if pType = MIGRATION_DONE // Call your scripts redraw functions to show the changed state to the user endif // Don't forget to re-position the IMPORT knob SetKnobValue 2,0 // This sample code is for type 3) (own slower timer, not running // after OnLoad), we need to restore the original settings SetTimerInterval 1000 StopTimer @End ==== @MigrateExportDone ==== At the end of an exportingtransmission started by a call to @MigrationManagerOnLoad in the @OnLoad, the @MigrateExportDone event is called by the Migration Manager. If your script is of type 3), then this is the place to undo the temporary timer settings @MigrateExportDone // param pType (either MIGRATION_DONE or MIGRATION_ERROR) // This sample code is for type c) (own slower timer, not running // after OnLoad), we need to restore the original settings SetTimerInterval 1000 StopTimer @End \\ \\ ===== Migration Manager Include Snippet ==== // ╔╦╗┬┌─┐┬─┐┌─┐┌┬┐┬┌─┐┌┐┌ ╔╦╗┌─┐┌┐┌┌─┐┌─┐┌─┐┬─┐ // ║║║││ ┬├┬┘├─┤ │ ││ ││││ ║║║├─┤│││├─┤│ ┬├┤ ├┬┘ // ╩ ╩┴└─┘┴└─┴ ┴ ┴ ┴└─┘┘└┘ ╩ ╩┴ ┴┘└┘┴ ┴└─┘└─┘┴└─ // =================== V 1.2 ====================== @MigrationManagerInit MIGRATION_RUNNING = -1 // mmState and message type MIGRATION_ERROR = -2 MIGRATION_DONE = -7 MM_MAGIC = 0 // Offsets in global97[ ] MM_READER = 1 MM_SCRIPT = 2 MM_TYPE = 3 MM_ACK = 4 MM_SENDER = 5 MM_MAGIC_ID = 0x15EEC0DE // Magic number for MigrationManager mmActive = NO mmRead = NO mmState = MIGRATION_DONE mmInstance = Random 1, 1000 if Unassigned mmScriptTimer mmScriptTimer = NO endif mmIsMigrating = NO @End @MigrationManagerOnLoad if Unassigned mmScriptId Log {📦 MigrationManager v1.2: ‼️ mmScriptId not initialized} elseif Unassigned mmIsMigrating Log {📦 MigrationManager v1.2: ‼️ @MigrationManagerInit not called before @MigrationManagerOnLoad} else mmIsMigrating = NO // Starting with about 7 hex digits, Mozaic can no longer correctly add small // values due to its limited internal number range // As the unique script id is later incremented for each message, better test // beforehand, if the value is still in range for math operations mmIdx = mmScriptId +1 if mmIdx - mmScriptId <> 1 Log {📦 MigrationManager v1.2: ‼️ mmScriptId value too high} else mmIdx = mmScriptId if global97[MM_MAGIC]<>MM_MAGIC_ID Log {📦 MigrationManager v1.2: OnLoad - No receiver waiting} elseif global97[MM_SCRIPT]<>mmScriptId Log {📦 MigrationManager v1.2: OnLoad - Wrong receiver waiting} elseif global97[MM_SENDER]<>0 or global97[MM_TYPE]<>0 Log {📦 MigrationManager v1.2: OnLoad - Another migration ongoing} else global97[MM_SENDER] = mmInstance mmState = MIGRATION_RUNNING mmActive = YES mmRead = NO mmTimeout = SystemTime + 500 mmIsMigrating = YES if mmScriptTimer = NO Log {📦 MigrationManager v1.2: Start Migration with own timer event} SetTimerInterval 50 StartTimer else Log {📦 MigrationManager v1.2: Start Migration using scripts timer event} endif endif endif endif @End @MigrationManagerOnImport if Unassigned mmScriptId Log {📦 MigrationManager v1.2: ‼️ mmScriptId not initialized} elseif Unassigned mmIdx Log {📦 MigrationManager v1.2: ‼️ @MigationManagerOnLoad was not called} elseif mmState<>MIGRATION_RUNNING FillArray global97, 0 FillArray global98, 0 global97[MM_MAGIC] = MM_MAGIC_ID global97[MM_READER] = mmInstance global97[MM_SCRIPT] = mmScriptId // MM_SENDER, MM_TYPE and MM_ACK are zero fromm the FillArray above mmActive = YES mmRead = YES mmState = MIGRATION_RUNNING mmTimeout = SystemTime + 10000 if mmScriptTimer = NO Log {📦 MigrationManager v1.2: Waiting for data with own timer event} SetTimerInterval 50 StartTimer else Log {📦 MigrationManager v1.2: Waiting for data using scripts timer event} endif mmIsMigrating = YES endif if not mmActive mmIsMigrating = NO pType = MIGRATION_ERROR Call @MigrateImportDone endif @End @OnTimer Call @MigrationManagerOnTimer @End @MigrationManagerOnTimer if Unassigned mmActive mmActive = NO endif if mmActive Call @MMTimerVerifyConnection if mmState <> MIGRATION_ERROR if mmRead Call @MMTimerRead else Call @MMTimerSend endif endif if SystemTime > mmTimeout Log {📦 MigrationManager v1.2: ‼️ Timeout} mmState = MIGRATION_ERROR if mmScriptTimer = NO StopTimer endif endif if mmState <> MIGRATION_RUNNING mmIsMigrating = NO if mmRead pType = mmState Call @MigrateImportDone else pType = mmState Call @MigrateExportDone endif if mmScriptTimer = NO StopTimer endif if mmRead or mmState = MIGRATION_ERROR FillArray global97,0 FillArray global98,0 mmActive = NO endif endif endif @End @MMTimerVerifyConnection if global97[MM_MAGIC]<>MM_MAGIC_ID mmState = MIGRATION_ERROR Log {📦 MigrationManager v1.2: ‼️ Communication broken - Magic of msg block changed} elseif global97[MM_SCRIPT]<>mmScriptId mmState = MIGRATION_ERROR Log {📦 MigrationManager v1.2: ‼️ Communication broken - ScriptId of msg block changed} elseif (not mmRead) and global97[MM_SENDER]<>mmInstance mmState = MIGRATION_ERROR Log {📦 MigrationManager v1.2: ‼️ Communication broken - SenderId of msg block changed} elseif mmRead and global97[MM_READER]<>mmInstance mmState = MIGRATION_ERROR Log {📦 MigrationManager v1.2: ‼️ Communication broken - ReaderId of msg block changed} endif @End @MMTimerSend // OnLoad if global97[MM_TYPE]=global97[MM_ACK] pIdx = mmIdx pType = MIGRATION_ERROR Call @MigrateSend global97[MM_TYPE] = pType if pType = MIGRATION_ERROR mmState = pType Log {📦 MigrationManager v1.2: ‼️ MigrateSend didn't set pType for pIdx=},pIdx elseif pType = MIGRATION_DONE mmState = pType Log {📦 MigrationManager v1.2: Migration succesful} else Log {📦 MigrationManager v1.2: - Send },mmIdx - mmScriptId mmIdx = mmIdx + 1 // Inc clips at 65536 per default mmTimeout = SystemTime + 250 endif endif @End @MMTimerRead // OnImport pType = global97[MM_TYPE] if pType=MIGRATION_DONE mmState = pType Log {📦 MigrationManager v1.2: Migration succesful} elseif pType=MIGRATION_ERROR mmState = pType Log {📦 MigrationManager v1.2: ‼️ Communication broken} elseif pType<>0 and pType<>global97[MM_ACK] Call @MigrateRead global97[MM_ACK] = pType Log {📦 MigrationManager v1.2: - Read },pType - mmScriptId mmTimeout = SystemTime + 250 endif @End // =================== END ============================ // ╔╦╗┬┌─┐┬─┐┌─┐┌┬┐┬┌─┐┌┐┌ ╔╦╗┌─┐┌┐┌┌─┐┌─┐┌─┐┬─┐ // ║║║││ ┬├┬┘├─┤ │ ││ ││││ ║║║├─┤│││├─┤│ ┬├┤ ├┬┘ // ╩ ╩┴└─┘┴└─┴ ┴ ┴ ┴└─┘┘└┘ ╩ ╩┴ ┴┘└┘┴ ┴└─┘└─┘┴└─