Questions about setting selection as a group of notes, and more

I intend to make a script that will be able to streamline my workflow of recognising phonemes and adjusting their lengths accordingly to whether they’re vowels, consonants or none of the above (e.g. breaths), and I’ve got a few things I’m wondering about.

  1. I’ve looked through the manual, and I’m confused if there’s a way to make a group out of a selection of notes in code
  2. This is because I intend to set a selection as a group, gather phoneme data via getPhonemesOfGroup, edit durations of phonemes accordingly note by note, then somehow dissociate that group automatically. If that’s possible too.
  3. I’m aware that simply “getPhonemes” isn’t going to work since default phoneme settings will just set the function to return null. My mind is wondering if there’s a way to trick the program into thinking it isn’t default, so getPhonemes will just return the phonemes of each note anyway.

I have no idea why getPhonemes behavior isn’t changed yet, since other people I’ve seen in this forum seem like they want it to return the phonemes of a note anyway, default settings or not. Am tagging this as product feedback, because of it.

You can’t group notes via scripts, but you also don’t need to. The first NoteGroupReference in a Track represents that track’s ungrouped notes (ie the “Main Group”).

Internally, all notes are part of a group, even if they aren’t presented that way in the UI.

See Track - Synthesizer V Studio Scripting Manual

As an example:

  var editorView = SV.getMainEditor();
  var selection = editorView.getSelection();
  var selectedNotes = selection.getSelectedNotes();
  if (selectedNotes.length == 0) {
    return;
  }

  var currentGroupRef = editorView.getCurrentGroup();
  var groupPhonemes = SV.getPhonemesForGroup(currentGroupRef);

  var phonemes = [];
  for (var i = 0; i < selectedNotes.length; i++) {
    var noteIndex = selectedNotes[i].getIndexInParent();
    phonemes.push(groupPhonemes[noteIndex]);
  }

This produces an array (phonemes) where the elements map to each selected note’s phoneme sequence. ie. the first Note in the selection’s phonemes will be accessible with phonemes[0].

(of course, if using LUA, indexes start at 1 instead of zero. LUA is generally a better option in SynthV Studio scripting for stability reasons, I just used a JS example because it’s easier for me to type out something intelligible on my phone with a syntax I’m more used to)

(while this example doesn’t directly get the track’s first NoteGroupReference with getGroupReference(0), the point I’m trying to demonstrate is that SV.getMainEditor().getCurrentGroup() will still return the NoteGroupReference for your selected notes, even if you haven’t “grouped” those notes in the UI)

1 Like

Appreciate the help! Worked like a charm, saved me a ton of headache. Been taking some shots at the code and the phoneme part is settled for now, but the JavaScript is behaving a bit funny.

var consoBase = ["b", "ch", "d", "dx", "dr", "dh", "f", "g", "hh", "jh", "k", "l", "m", "n", "ng", "p", "r", "s", "sh", "t", "tr", "th", "v", "w", "y", "z", "zh"];
var testConsonant = consoBase.includes("p");
SV.showMessageBox("Debug station", testConsonant);

Apparently this is enough to return TypeError, where it says that I didn’t define consoBase as an array and it can’t call the .includes method. I tried it on a separate JS compiler and it printed out well on a console.

Maybe this is one of the stability issues that switching to Lua might fix? :sweat_smile:
(I did do a pseudoscientific sanity check on ChatGPT and it gaslighted me that it’s perfectly fine)

The embedded JavaScript used by Synthesizer V Studio does not include any features added in ES6 or higher. This is a (non-exhaustive) list of ES6 additions: JavaScript ES6 (w3schools.com).

From the documentation:

Supported Languages

The classic (ES5.1) equivalent to Array.prototype.includes is as follows:

   var myArray = [ 1, 2, 3, 4 ];
   SV.showMessageBox("Array contains 3?", myArray.indexOf(3) > -1); // true
   SV.showMessageBox("Array contains 6?", myArray.indexOf(6) > -1); // false

If a value is in the array, indexOf returns the index, otherwise it returns -1.


The stability issues I mentioned above aren’t to do with the actual execution of the code, just a compatibility issue with Logic Pro (I think specifically devices using an Apple ARM processor).

JavaScript is also more strictly sandboxed than LUA in SynthV Studio. Embedded LUA can write files, for example, while JavaScript cannot access the file system at all.

1 Like

Got it now :+1: here’s what I have so far with your help. Runs pretty smoothly and I’m interested to see how this’ll pan out in my workflow.

function getClientInfo() {
    return {
      "name" : "Vowel-Consonant Adjustment",
      "category" : "Tests",
      "author" : "Fyrebreak",
      "versionNumber" : 1,
      "minEditorVersion" : 65537
    };
  }
  
  function operation(){
    
    var selection = SV.getMainEditor().getSelection();
    var selectedNotes = selection.getSelectedNotes();
    if (selectedNotes.length == 0) {
        SV.showMessageBox("Notice","No notes selected. Please select a note.");
        return;
    }

    var currentGroupRef = SV.getMainEditor().getCurrentGroup();
    var groupPhonemes = SV.getPhonemesForGroup(currentGroupRef);

    var phonemes = [];
    for (var i = 0; i < selectedNotes.length; i++) {
        var noteIndex = selectedNotes[i].getIndexInParent();
        phonemes.push(groupPhonemes[noteIndex]);
    }

    var userInput = {
      "title" : "Adjust phoneme length",
      "message" : "Mass adjustment of vowels and consonants.",
      "buttons" : "OkCancel",
      "widgets" : [
        /*{
            "name" : "override", "type" : "CheckBox",
            "text" : "Override values that are not at 100%",
            "default" : false
          },*/
        {
          "name" : "vowelDuration", "type" : "Slider",
          "label" : "Vowel Duration Scale",
          "format" : "%1.0f",
          "minValue" : 20,
          "maxValue" : 180,
          "interval" :5,
          "default" : 100
        },
        {
            "name" : "consonantDuration", "type" : "Slider",
            "label" : "Consonant Duration Scale",
            "format" : "%1.0f",
            "minValue" : 20,
            "maxValue" : 180,
            "interval" :5,
            "default": 100
          },
        {
        "name" : "breathDuration", "type" : "Slider",
        "label" : "Breath Duration Scale",
        "format" : "%1.0f",
        "minValue" : 20,
        "maxValue" : 180,
        "interval" :5,
        "default": 100
        }
          /*,{
            "name" : "vowelStrength", "type" : "Slider",
            "label" : "Vowel Strength Scale",
            "format" : "%1.0f",
            "minValue" : 20,
            "maxValue" : 180,
            "interval" :5,
            "default" : 100
          },
          {
              "name" : "consonantStrength", "type" : "Slider",
              "label" : "Consonant Strength Scale",
              "format" : "%1.0f",
              "minValue" : 20,
              "maxValue" : 180,
              "interval" :5,
              "default": 100
            },
        {
            "name" : "breathStrength", "type" : "Slider",
            "label" : "Breath Strength Scale",
            "format" : "%1.0f",
            "minValue" : 20,
            "maxValue" : 180,
            "interval" :5,
            "default": 100
            },*/
      ]
    };
    
    var result = SV.showCustomDialog(userInput);

    if (result.status == "Cancel") return;
    
    var vowelBase = ["aa", "ae", "ah", "ao", "aw", "ax", "ay", "eh", "er", "ey", "ih", "iy", "ow", "oy", "uh", "uw"];
    var consoBase = ["b", "ch", "d", "dx", "dr", "dh", "f", "g", "hh", "jh", "k", "l", "m", "n", "ng", "p", "r", "s", "sh", "t", "tr", "th", "v", "w", "y", "z", "zh"];
    var silentBase = ["q", "dw", "tw", "cl", "pau", "sil", "br"];

    for (var i = 0; i < selectedNotes.length; i++){
        var notePhonemes = phonemes[i].split(" ");
        //SV.showMessageBox("Debug station", "Entering loop");
        var durEdit = [];
        for (var j = 0; j < notePhonemes.length; j++){
            var phoneme = notePhonemes[j];
            //SV.showMessageBox("Debug third station", notePhonemes[0]);
           //SV.showMessageBox("Entering phoneme stage", result.answers.consonantDuration + " " + phoneme);
            if(vowelBase.indexOf(phoneme)>-1){
              //SV.showMessageBox("Debug station", "Vowel");
                durEdit.push(result.answers.vowelDuration/100);
            }
            else if (consoBase.indexOf(phoneme)>-1){
                //SV.showMessageBox("Debug station", "Consonant");
                durEdit.push(result.answers.consonantDuration/100);
            }
            else if (silentBase.indexOf(phoneme)>-1){
                durEdit.push(result.answers.breathDuration/100);
            }
        }
        selectedNotes[i].setAttributes({
            "dur": durEdit
        });
    }
    
    
    
    

    /* if(result.status == "Yes") {
      SV.showMessageBox("Filled Form", "Slider value: " + result.answers.sl1);
      SV.showMessageBox("Filled Form", "ComboBox values: " +
        result.answers.cb1 + " and " + result.answers.cb2);
      SV.showMessageBox("Filled Form", "TextBox value: " + result.answers.tb1);
      SV.showMessageBox("Filled Form", "TextArea value: " + result.answers.ta1);
      SV.showMessageBox("Filled Form", "CheckBox1: " + result.answers.check1);
      SV.showMessageBox("Filled Form", "CheckBox2: " + result.answers.check2);*/
        
    }

  function main() {
   operation();
    SV.finish();
  }

I would like to do similar adjustments to phoneme strength as well, but there isn’t an equivalent of the “dur” array for duration to adjust strength value, written in the manual. Any idea if it’s just called “str” or something?

image
Just putting this for anyone’s reference if they find it useful
I found out that the title of the strength array is called “strength”, and I’ll be messing around with it to find out how to mass adjust strength conveniently across all notes in question.
Might be the case that strength’s parameters are also initialized as null when at their default values.

1 Like

Looks like the strength values go from 0.2 to 1.8. In cases where no values are set, it is an empty array, and if only some strengths are set, any prior values will default to 1 while following values will not be included.

For example, setting a three-phoneme note (via the UI sliders) to default / 50% / default will return a value of [1, 0.5], where the last phoneme is absent.

Setting values does not follow this same rule – you must provide all strengths in the array or else the ones missing will be reset to 1.

For example, this will set the first three phoneme strengths. If there is a fourth phoneme, its strength is missing and so will be reset to default (100%).

  note.setAttributes({
    strength: [ 0.2, 0.5, 1.7 ]
  });
1 Like