An efficient method for assigning numeric inputs into %output%

I had been working on a way to select/input cover size for an iTunes source script for quite some time; and with the new features of Mp3Tag v3.22+, I developed a method to do it (more or less).

Since this method can work with any number, and I was unable to find any topic (other than my own) regarding this subject, I've created this topic to explain how I did it, so other authors can benefit from it.

Until v3.22, there was only one field available for direct user input - [SearchBy]. With v3.22, the introduction of a settings schema now allowed adding string, boolean and integer values from outside of a script, and the ability to refer to them using ifVar or ifNotVar. For example, setting the number '5' in the schema, like

{
  "type": "number",
  "key": "testNumber",
  "title": "Something to enter a number",
  "description": "Enter a number",
  "default": 42
},

creates a input field with "42" pre-written (but this can be replaced with "5"). This field could then be called from the script, like

IfVar "testNumber" "5"
...
Say "5"
...
EndIf

in a simple manner. In this example, the number '5' is indirectly 'translated' from a 'Settings' value into a script value. With a minor tweak, it could even be stored in an output, like

IfVar "testNumber" "5"
   OutputTo "testNumber"
   Say "5"
EndIf

so that this value could now be called further down with sayOutput "testNumber".

But this only works for a single number. Typing any number other than '5' in the input field won't return any value in the script. For it to work with a number range, ifVar conditions must be set for each number in the range. For a range [0-9}, this would mean

IfVar "testNumber" "0"
   OutputTo "testNumber"
   Say "0"
EndIf
IfVar "testNumber" "1"
   OutputTo "testNumber"
   Say "1"
EndIf
IfVar "testNumber" "2"
   OutputTo "testNumber"
   Say "2"
EndIf
...
IfVar "testNumber" "9"
   OutputTo "testNumber"
   Say "9"
EndIf

So, 4 lines of code per digit x 10 possible numbers = 40 lines of code for a single-digit number to be translated. This can be further optimized, by replacing both outputTo "testNumber" and say "N" commands with set "testNumber" "N". So, 3 lines * 10 = 30 lines of code; which is perfectly doable.

With a 2-digit number range [00-99], it would take 3 * 100 = 300 lines of code; which is unpractical, but still doable. Bigger numbers (like a cover size in pixels, which range in the thousands) would be extremely impractical and very resource-heavy for a script to process.

An easier way to translate 2-digit numbers is to split them into 2 single-digit inputs in the schema, translate each one separately into the script with ifVar, and then join the 2 outputs back into a 2-digit number, like

OutputTo "fullNumber"
   Say "testNumber1"
   Say "testNumber2"

Translating the number "42" can now be done by inputting "4" into "testNumber1" and "2" into "testNumber2" in the schema, using 2 sets of ifVar for each digit, and ending with outputTo "fullNumber" to merge the 2 digits back into "42". This would take 3*10 * 2 (sets of ifVar) + 3 lines for outputting "fullNumber" = 63 lines of code. Which is far less than the 300 lines needed before, but is still quite a sum.

I've improved this concept, and managed to translate a 4-digit number in 23 lines of code. It requires use of Mp3Tag 3.23, JSON tools and %output% buffer referral. If you don't know how these tools works, you may not fully understand the following.

For a 2-digit number (like "42"), on the configuration schema side, simply create 2 number input fields; one for each digit of the number we want. That's all that is necessary from this end.

In the script, using json_foreach, it's possible to iterate over a JSON-formatted data array and perform actions for each element in that array. If our input data had an array with the single-digit numbers, it could be accessed and parsed into "testNumberN".

But normally, input data doesn't have those values, or it may not be JSON data at all.

What to do? Write our own JSON array!
How?

First, we clear the current buffer; this can be achieved with a regular expression (you may want to learn about those too) like

RegexpReplace "^.*" ""

Next, we write a JSON-formatted array into the buffer; for efficiency, I'm adding this step into the previous one, like

RegexpReplace "^.*" "{"array":[{"number":0},{"number":1},{"number":2},{"number":3},{"number":4},{"number":5},{"number":6},{"number":7},{"number":8},{"number":9}]}"

Next, we need to tell Mp3Tag to consider the text currently in the buffer as JSON data, which is done with

json "on" "current"

Now we are working in JSON "mode" and have a JSON array with [0-9]. Next we'll check its contents sequentially and use them as needed. Let's say

json_foreach "array"
   json_select "number"
   OutputTo "digit"
   SayRest
json_foreach_end

This reads -- for all elements in the array, select the "number" elements, and write them to "digit"--.
The output digit will be filled with each value in the array, step by step: "0", then "01", 012", "0123", "01234", "012345", "0123456", "01234567", "012345678", and "0123456789".

Now lets add an ifVar

json_foreach "array"
   json_select "number"
   OutputTo "digit"
   SayRest

   IfVar "testNumber1" "%digit%"
	   OutputTo "testNumber1"
	   SayRest
   EndIf

json_foreach_end

This is the same ifVar command as shown before; the only difference is that instead of comparing "testNumber1" to a number, it is now comparing with the current value of "digit" (which is being filled step-by-step with the values in the array, which are numbers). Going back to our number "42", which was split into "testNumber1" (4) and "testNumber2" (2), this loop now processes as follows

Loop 1: Select array value "0" / Output to "digit":"[empty]" / Write "0" into "digit" / IfVar "testNumber1" (4) is %digit%" (0) / false
Loop 2: Select array value "1" / Output to "digit":"0" / Write "1" into "digit" / IfVar "testNumber1" (4) is %digit%" (01) / false
Loop 3: Select array value "2" / Output to "digit":01 / Write "2" into "digit" / IfVar "testNumber1" (4) is %digit%" (012) / false
Loop 4: Select array value "3" / Output to "digit":"012" / Write "3" into "digit" / IfVar "testNumber1" (4) is %digit%" (0123) / false
...
Loop 10: Select array value "9" / Output to "digit":"012345678" / Write "9" into "digit" / IfVar "testNumber1" (4) is %digit%" (0123456789) / false

Our output "testNumber1" stayed empty because ifVar never matched "testNumber1" (4) with "digit", because the contents of "digit" were being appended to the end of the its current value.
Let's try again, but now we clear "digit" at the end of each loop:

json_foreach "array"
   json_select "number"
   OutputTo "digit"
   SayRest

   IfVar "testNumber1" "%digit%"
     OutputTo "testNumber1"
     SayRest
   EndIf

   Set "digit"
json_foreach_end

Running our (modified) loop again

Loop 1: Select array value "0" / Output to "digit":"[empty]" / Write "0" into "digit" / IfVar "testNumber1" (4) is %digit%" (0) / false / Clear "digit":"0"
Loop 2: Select array value "1" / Output to "digit":"[empty]" / Write "1" into "digit" / IfVar "testNumber1" (4) is %digit%" (1) / false / Clear "digit":"1"
Loop 3: Select array value "2" / Output to "digit":"[empty]" / Write "2" into "digit" / IfVar "testNumber1" (4) is %digit%" (2) / false / Clear "digit":"2"
Loop 4: Select array value "3" / Output to "digit":"[empty]" / Write "3" into "digit" / IfVar "testNumber1" (4) is %digit%" (3) / false / Clear "digit":"3"
Loop 5: Select array value "4" / Output to "digit":"[empty]" / Write "4" into "digit" / IfVar "testNumber1" (4) is %digit%" (4) / TRUE / OutputTo "testnumber1":"[empty]" / Write "4" into "testnumber1" / Clear "digit":"4"
Loop 6: ...
...
Loop 10: ...

Success! "4" has been written into "testNumber1". The rest of the loop will still run, but since there will be no match, it isn't important.

As we have done for "testNumber1", let's add "testNumber2" and check both at the same time

json_foreach "array"
   json_select "number"
   OutputTo "digit"
   SayRest

   IfVar "testNumber1" "%digit%"
      OutputTo "testNumber1"
      SayRest
   EndIf

   IfVar "testNumber2" "%digit%"
      OutputTo "testNumber2"
      SayRest
   EndIf

   Set "digit"
json_foreach_end

Now, after "testnumber1" (4), "testnumber2" (2) will be compared with the current value of "digit". When it matches, that value is written into its dedicated output "testNumber2". When it doesn't match, the loop keeps running until it does, then continues until the last array element has been checked.

Finally, after having both outputs (now set at "4" and "2"), we can do as before

OutputTo "fullNumber"
	Say "testNumber1" (4)
	Say "testNumber2" (2)

And we get our "fullNumber" (42)

The entire code from start to finish reads

RegexpReplace "^.*" "{"array":[{"number":0},{"number":1},{"number":2},{"number":3},{"number":4},{"number":5},{"number":6},{"number":7},{"number":8},{"number":9}]}"

json "on" "current"

json_foreach "array"
	json_select "number"
	OutputTo "digit"
	SayRest

	IfVar "testNumber1" "%digit%"
		OutputTo "testNumber1"
		SayRest
	EndIf

	IfVar "testNumber2" "%digit%"
		OutputTo "testNumber2"
		SayRest
	EndIf

	Set "digit"
json_foreach_end

OutputTo "fullNumber"
	Say "testNumber1"
	Say "testNumber2"

A 2-digit integer has indeed been translated from 2 user input fields from the Settings menu into a "fullNumber" output that can now be called as needed. And (after removing whitespaces) this was done in 19 lines of code.
Which is much less than 63, and much much less than 300.

If you've read my previous post regarding this theme, I had been asking for a "sayVar" command to directly write input fields into the current buffer. But this method is a better fit with a "setVar" command, so that

setVar "testNumber1" "testNumber1"

would translate the "testNumber1" value from the schema into a "testNumber1" output.

As I said before, I've worked this principle into my own WS script for iTunes, and managed to get a custom 4-digit number into a working value for setting cover size, and using only 23 lines of code.

If you are a fellow author and can use this method for your own needs please do so. Feedback is not necessary, but would be appreciated.

Thank you.

Here is a simpler (and a little more efficient) method; one that doesn't require the use of JSON tools at all (and thus is simpler).

It follows the same method as before (loop through a set of digits, until a match to a Settings' value is found, and assign it to an output to be later combined into a number); however the command called to run this loop shifts from json_foreach to Do ... While.

Since we are no longer working with JSON data, our set of digits must not be a JSON-formatted array; but a plain string of digits (and separators), which can be written simply by

RegexpReplace "^.*" ",0,1,2,3,4,5,6,7,8,9"

You'll notice that the leading character in the string is a separator - , in this case. This is needed for the loop to run properly.

Now, the same script as in the previous post, with the new changes:

RegexpReplace "^.*" ",0,1,2,3,4,5,6,7,8,9"

Do
	OutputTo "digit"
	MoveChar 1
	SayUntil ","

	IfVar "testNumber1" "%digit%"
		OutputTo "testNumber1"
		SayRest
	EndIf

	IfVar "testNumber2" "%digit%"
		OutputTo "testNumber2"
		SayRest
	EndIf

	Set "digit"
While ","

OutputTo "fullNumber"
	Say "testNumber1"
	Say "testNumber2"

Because the first action applied to the pointer in the loop is "MOVE pointer BY 1", the first character must not be a part of our set of digits. I used a separator in this example, but it could just as well be any one (and only one) character. Now the pointer is resting on "0", and will read the string until , is found, after which it will rest on another ,. At the end of each loop While will check if the pointer is on a separator (it hasn't moved, so it is), and start another loop.

At the last digit "9", SayUntil "," will read the rest of the string (because there is no trailing separator to stop it), but since only the final digit remains, that is all that will be read (and is what is wanted); the pointer will no longer rest on a separator, so While will fail its check, and the loop will be broken.

The separator used here is , but could just as well be any character (or string), as long as not present in the digits we're working with.

So then:

18 lines of code. Because there is no need for

json "on" "current"

this step can be removed.

As mentioned at the start of this post, this method is simpler (no JSON required), and a little more efficient (cutting 1 step from the previous method).

If you find this useful, please use it. If you have suggestions on how to improve it, please share them.

And thank you.

I just went through the workaround again, this is incredible!

I've just released Mp3tag v3.32 with support for SayVar (as described here).

It actually reminds me of a how to break a number lock and take the bicycle away :wink:.

I can still remember vividly when -after multiple failed attempts- I stumbled upon found the right sequence of commands for this particular task.

And now I've been 1-upped by @Florian :trophy:
Thank You :raising_hands:

After the police gave me a stern warning to put an end to my bicycle-stealing career, I finally found a way to make a better use of those skills. :zany_face: :rofl:

(This is a humorous joke, obviously :wink:).