diff --git a/dist/articulation_autoposition_rolled_chords.lua b/dist/articulation_autoposition_rolled_chords.lua index 118c33f6..70c08bb1 100644 --- a/dist/articulation_autoposition_rolled_chords.lua +++ b/dist/articulation_autoposition_rolled_chords.lua @@ -869,7 +869,7 @@ Uses `FCArticulation.CalcMetricPos` to determine if the input articulation is on : (boolean) true if on note-side, otherwise false ]] function articulation.is_note_side(artic, curr_pos) - if nil == curr_pos then + if not curr_pos then curr_pos = finale.FCPoint(0, 0) if not artic:CalcMetricPos(curr_pos) then return false diff --git a/docs/rgp-lua/assets/additem.jpg b/docs/rgp-lua/assets/additem.jpg index 0b303607..d1d5bf03 100644 Binary files a/docs/rgp-lua/assets/additem.jpg and b/docs/rgp-lua/assets/additem.jpg differ diff --git a/docs/rgp-lua/finaleplugin-properties.md b/docs/rgp-lua/finaleplugin-properties.md index 49c13a12..a36c4101 100644 --- a/docs/rgp-lua/finaleplugin-properties.md +++ b/docs/rgp-lua/finaleplugin-properties.md @@ -262,15 +262,78 @@ or finaleplugin.Id = "742d0ea0-c109-4b81-87ae-d059f27cb028" ``` +Additional Menu Options +======================= + +Frequently you may wish to have shortcuts to functions that differ from each other by only one or a few variables. For example, you might wish to have a script that transposes a selected music region up an octave. If you then wanted another script to transpose a region *down* an octave, you might duplicate the up-octave script and change a single value in it. You could instead reuse the first script by defining the interval value in a variable. This would allow for separate configurations of the script in the [configuration dialog](/docs/rgp-lua/rgp-lua-configuration), but it still would require end-users to set up the configurations for themselves. And they would have to be somewhat knowledgable of Lua syntax to do it. + +_RGP Lua_ version 0.62 introduces the concept of **Additional Menu Options**. These allow a script to configure multiple versions of itself to appear in Finale's Plug-ins menu. The setup of Additional Menu Options is similar to the setup of the deprecated [Parameters](parameters-deprecated) fields in _JW Lua_. Each of the necessary fields (menu option text, undo text, description, and prefix) appear in parallel lists delimited by line breaks. Each list is a multiline string value in the `finaleplugin` namespace. + +_JW Lua_ does not support Additional Menu Options. It loads only the base menu option of the script, even if Additional Menu Options are supplied. + +#### AdditionalMenuOptions (multiline string) + +The text for each of the menu options to be added (in addition to the main menu option returned by the `plugindef()` function. Each line of the string represents a menu option to be added. Whitespace is ignored. + +```lua +finaleplugin.AdditionalMenuOptions = [[ + Transpose Octave Down + Transpose Third Up + Transpose Third Down +]] +``` + +#### AdditionalUndoText (multiline string) + +The undo text corresponding to each of the additional menu options defined in the `AdditionalMenuOptions` field. This field may be omitted, and _RGP Lua_ then uses each menu item text for its corresponding undo text. + +```lua +finaleplugin.AdditionalUndoText = [[ + Transpose Octave Down + Transpose Third Up + Transpose Third Down +]] +``` + +#### AdditionalDescriptions (multiline string) + +The description text corresponding to each of the additional menu options defined in the `AdditionalMenuOptions` field. If you omit these fields, _RGP Lua_ uses the default description returned by the `plugindef()` function. It is highly recommended to supply separate description text for each additional menu option, but if your base description is generic enough to cover all the different menu options, then it is not necessary. + +```lua +finaleplugin.AdditionalDescriptions = [[ + Transposes the selected region an octave lower + Transposes the selected region a third higher + Transposes the selected region a third lower +]] +``` + +#### AdditionalPrefixes (multiline string) + +The prefixes corresponding to each of the additional menu options defined in the `AdditionalMenuOptions` field. Each prefix is a line of Lua code that defines how the script should behave for that menu option. Keep in mind that, if necessary, you can define more than one variable on a single line of Lua code, for example: + +```lua +var_a = 1 var_b = true var_c = "EVPU" +``` + +Since each prefix can be any Lua code you wish, the sky is pretty much the limit on what you can do with it. In the example below it is a simple variable assignment, but it could instead (or in addition) be a string that the script then `requires` or that contains a function name to execute. Or if there are so many variables to assign that a single line of Lua is confusing, you could define a configuration table inside the script and use the prefix to supply an index into the configuration table. + +Each additional prefix executes *in addition to* and *after* any prefix defined in the [configuration dialog](/docs/rgp-lua/rgp-lua-configuration). (Note, however, that _RGP Lua_ ignores the `AdditionalMenuOptions` fields if the configuration includes Optional Menu Text.) Each additional prefix also executes after any System Prefix, if defined. + +```lua +finaleplugin.AdditionalPrefixes = [[ + input_interval = -7 + input_interval = 2 + input_interval = -2 +]] +``` + Parameters (Deprecated) ======================= -**NOTE:** Parameters are not well supported by _JW Lua_ and not supported at all by _RGP Lua_. For _RGP Lua_ you can have a similar type of flexibility with a **prefix**. The parameter properties described here are **deprecated** and ignored by _RGP Lua_. +**NOTE:** Parameters are not well supported by _JW Lua_ and not supported at all by _RGP Lua_. For _RGP Lua_ you can have a similar type of flexibility with a **prefix** and/or [Additional Menu Options](additional-menu-options). The parameter properties described here are **deprecated** and ignored by _RGP Lua_. #### ParameterTypes (multiline string) -**NOTE:** Parameters are not well supported on _JW Lua_ and not supported at all on _RGP Lua_. For _RGP Lua_ you can get a similar type of funcitonality with a _prefix_. The parameter properties described here are **deprecated**.. - The types to the script parameters. The syntax is similar to the `SetTypes()` method when using [UserInputValue dialog input](http://jwmusic.nu/jwplugins/wiki/doku.php?id=jwlua:uservalueinput "jwlua:uservalueinput"). Use one line for each parameter. Don't use quotation marks around the types. The number of types must be identical to the number of descriptions. Valid types are `Number`, `String`, `Boolean` and `NumberedList`. Example: diff --git a/docs/rgp-lua/rgp-lua-configuration.md b/docs/rgp-lua/rgp-lua-configuration.md index 084a040a..cebf6d0a 100644 --- a/docs/rgp-lua/rgp-lua-configuration.md +++ b/docs/rgp-lua/rgp-lua-configuration.md @@ -39,6 +39,8 @@ Add/Edit Dialog **Select Folder.** Selects a folder containing `.lua` and/or `.luac` files as an Auto Folder. _RGP Lua_ inserts every `.lua` or `.luac` file in an Auto Folder into Finale's plugin menu except `mobdebug.lua`, if it happens to exist. +**Active.** If this option is unchecked, _RGP Lua_ skips this item when Finale loads. It allows you keep items in the list even if you do not wish them to appear in Finale. You can activate them later if you wish them to appear in Finale again. + **Enable Debugging.** Selecting this option enables RGP Lua debugging features. These include: * Embedding the [`luasocket`](https://aiq0.github.io/luasocket/index.html) library into the Lua machine as global `socket` before executing the script. This allows communication with an external debugger, but it also gives affected scripts access to your network and potentially the Internet, so only use it with scripts from trusted sources. @@ -46,13 +48,13 @@ Add/Edit Dialog **Load As String.** Normally _RGP Lua_ sends the entire script file to the Lua interpreter. However, it is possible to include a non-Lua appendix delimited by a `NULL` character in the file. Selecting this option causes _RGP Lua_ to first load the file into a string and send that string to the Lua interpreter. The Lua interpreter then stops at the `NULL` character. A script file can override this option by setting `finaleplugin.LoadAsString` in the `plugindef()` function. -**Optional Menu Text.** Specifies the menu text to be used with this instance of the selected script file. If omitted, _RGP Lua_ uses the menu text returned by the `plugindef()` function in the script. This option is not available for Auto Folders. +**Optional Menu Text.** Specifies the menu text to be used with this instance of the selected script file. If omitted, _RGP Lua_ uses the menu text returned by the `plugindef()` function in the script. If Optional Menu Text is supplied, this instance of the script will not load any `finaleplugin.AdditionalMenuOptions` configured in the `plugindef()` function. This option is not available for Auto Folders. -**Optional Undo Text.** Specifies the undo text to be used with this instance of the selected script file. If omitted, _RGP Lua_ uses the undo text returned by the `plugindef()` function in the script. This option is not available for Auto Folders. +**Optional Undo Text.** Specifies the undo text to be used with this instance of the selected script file. If omitted, _RGP Lua_ uses the Optional Menu Text if it exists. Otherwise it uses the undo text returned by the `plugindef()` function in the script. This option is not available for Auto Folders. **Optional Description.** Specifies the description to be used with this instance of the selected script file. If omitted, _RGP Lua_ uses the description returned by the `plugindef()` function in the script. This option is not available for Auto Folders. -**Prefix.** This is a multi-line code window that allows you to enter a brief snippet of Lua code. If a prefix exists, _RGP Lua_ executes it in the Lua environment before executing the script file. One use case for it would be to initialize some global variables that the script uses as input parameters. The script inherits any global values set by the prefix. +**Prefix.** This is a multi-line code window that allows you to enter a brief snippet of Lua code. If a prefix exists, _RGP Lua_ executes it in the Lua environment before executing the script file. One use case for it would be to initialize some global variables that the script uses as input parameters. Another might be to require a library for a script or an Auto Folder. Each script inherits any global values set by the prefix. This prefix executes immediately *after* the [System Prefix](system-prefix-dialog), if any. Note that you can select the same script file more than once and use the prefix and optional menu text, undo text, and description fields to tailor it to a specific use case. diff --git a/luaconfig.config b/luaconfig.config index ce20f46b..00840f3d 100644 --- a/luaconfig.config +++ b/luaconfig.config @@ -1,4 +1,4 @@ -column_limit: 120 +column_limit: 180 indent_width: 4 use_tab: false tab_width: 4 diff --git a/src/baseline_move_reset.lua b/src/baseline_move_reset.lua new file mode 100644 index 00000000..a6a29583 --- /dev/null +++ b/src/baseline_move_reset.lua @@ -0,0 +1,174 @@ +function plugindef() + finaleplugin.RequireSelection = true + finaleplugin.Author = "Robert Patterson" + finaleplugin.Version = "1.0" + finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" + finaleplugin.Date = "May 15, 2022" + finaleplugin.CategoryTags = "Baseline" + finaleplugin.AuthorURL = "http://robertgpatterson.com" + finaleplugin.MinJWLuaVersion = 0.62 + finaleplugin.Notes = [[ + This script nudges system baselines up or down by a single staff-space (24 evpus). It introduces 10 + menu options to nudge each baseline type up or down. It also introduces 5 menu options to reset + the baselines to their staff-level values. + + The possible prefix inputs to the script are + + ``` + direction -- 1 for up, -1 for down, 0 for reset + baseline_types -- a table containing a list of the baseline types to process + nudge_evpus -- a positive number indicating the size of the nudge + ``` + + You can also change the size of the nudge by creating a configuration file called `baseline_move.config.txt` and + adding a single line with the size of the nudge in evpus. + + ``` + nudge_evpus = 36 -- or whatever size you wish + ``` + + A value in a prefix overrides any setting in a configuration file. + ]] + finaleplugin.AdditionalMenuOptions = [[ + Move Lyric Baselines Up + Reset Lyric Baselines + Move Expression Baseline Above Down + Move Expression Baseline Above Up + Reset Expression Baseline Above + Move Expression Baseline Below Down + Move Expression Baseline Below Up + Reset Expression Baseline Below + Move Chord Baseline Down + Move Chord Baseline Up + Reset Chord Baseline + Move Fretboard Baseline Down + Move Fretboard Baseline Up + Reset Fretboard Baseline + ]] + finaleplugin.AdditionalDescriptions = [[ + Moves all lyrics baselines up one space in the selected systems + Resets all selected lyrics baselines to default + Moves the selected expression above baseline down one space + Moves the selected expression above baseline up one space + Resets the selected expression above baselines + Moves the selected expression below baseline down one space + Moves the selected expression below baseline up one space + Resets the selected expression below baselines + Moves the selected chord baseline down one space + Moves the selected chord baseline up one space + Resets the selected chord baselines + Moves the selected fretboard baseline down one space + Moves the selected fretboard baseline up one space + Resets the selected fretboard baselines + ]] + finaleplugin.AdditionalPrefixes = [[ + direction = 1 -- no baseline_types table, which picks up the default (lyrics) + direction = 0 -- no baseline_types table, which picks up the default (lyrics) + direction = -1 baseline_types = {finale.BASELINEMODE_EXPRESSIONABOVE} + direction = 1 baseline_types = {finale.BASELINEMODE_EXPRESSIONABOVE} + direction = 0 baseline_types = {finale.BASELINEMODE_EXPRESSIONABOVE} + direction = -1 baseline_types = {finale.BASELINEMODE_EXPRESSIONBELOW} + direction = 1 baseline_types = {finale.BASELINEMODE_EXPRESSIONBELOW} + direction = 0 baseline_types = {finale.BASELINEMODE_EXPRESSIONBELOW} + direction = -1 baseline_types = {finale.BASELINEMODE_CHORD} + direction = 1 baseline_types = {finale.BASELINEMODE_CHORD} + direction = 0 baseline_types = {finale.BASELINEMODE_CHORD} + direction = -1 baseline_types = {finale.BASELINEMODE_FRETBOARD} + direction = 1 baseline_types = {finale.BASELINEMODE_FRETBOARD} + direction = 0 baseline_types = {finale.BASELINEMODE_FRETBOARD} + ]] + return "Move Lyric Baselines Down", "Move Lyrics Baselines Down", "Moves all lyrics baselines down one space in the selected systems" +end + +local configuration = require("library.configuration") + +local config = {nudge_evpus = 24} + +if nil ~= configuration then + configuration.get_parameters("baseline_move.config.txt", config) +end + +local lyric_baseline_types = { + [finale.BASELINEMODE_LYRICSVERSE] = function() + return finale.FCVerseLyricsText() + end, + [finale.BASELINEMODE_LYRICSCHORUS] = function() + return finale.FCChorusLyricsText() + end, + [finale.BASELINEMODE_LYRICSSECTION] = function() + return finale.FCSectionLyricsText() + end, +} + +local find_valid_lyric_nums = function(baseline_type) + local lyrics_text_class_constructor = lyric_baseline_types[baseline_type] + if lyrics_text_class_constructor then + local valid_lyric_nums = {} + local lyrics_text_class = lyrics_text_class_constructor() + for i = 1, 32767, 1 do + if lyrics_text_class:Load(i) then + local str = finale.FCString() + lyrics_text_class:GetText(str) + if not str:IsEmpty() then + valid_lyric_nums[{baseline_type, i}] = 1 + end + end + end + return valid_lyric_nums + end + return nil +end + +function baseline_move() + local region = finenv.Region() + local systems = finale.FCStaffSystems() + systems:LoadAll() + + local start_measure = region:GetStartMeasure() + local end_measure = region:GetEndMeasure() + local system = systems:FindMeasureNumber(start_measure) + local lastSys = systems:FindMeasureNumber(end_measure) + local system_number = system:GetItemNo() + local lastSys_number = lastSys:GetItemNo() + local start_slot = region:GetStartSlot() + local end_slot = region:GetEndSlot() + + for _, baseline_type in pairs(baseline_types) do + local valid_lyric_nums = find_valid_lyric_nums(baseline_type) -- will be nil for non-lyric baseline types + for i = system_number, lastSys_number, 1 do + local baselines = finale.FCBaselines() + if direction ~= 0 then + baselines:LoadAllForSystem(baseline_type, i) + for j = start_slot, end_slot do + if valid_lyric_nums then + for lyric_info, _ in pairs(valid_lyric_nums) do + local _, lyric_number = table.unpack(lyric_info) + bl = baselines:AssureSavedLyricNumber(baseline_type, i, region:CalcStaffNumber(j), lyric_number) + bl.VerticalOffset = bl.VerticalOffset + direction * nudge_evpus + bl:Save() + end + else + bl = baselines:AssureSavedStaff(baseline_type, i, region:CalcStaffNumber(j)) + bl.VerticalOffset = bl.VerticalOffset + direction * nudge_evpus + bl:Save() + end + end + else + for j = start_slot, end_slot do + baselines:LoadAllForSystemStaff(baseline_type, i, region:CalcStaffNumber(j)) + -- iterate backwards to preserve lower inci numbers when deleting + for baseline in eachbackwards(baselines) do + baseline:DeleteData() + end + end + end + end + end +end + +-- parameters for additional menu options +baseline_types = baseline_types or {finale.BASELINEMODE_LYRICSVERSE, finale.BASELINEMODE_LYRICSCHORUS, finale.BASELINEMODE_LYRICSSECTION} +direction = direction or -1 +nudge_evpus = nudge_evpus or config.nudge_evpus + +baseline_move() diff --git a/src/beam_selected_region.lua b/src/beam_selected_region.lua index 1d5b7115..7666934f 100644 --- a/src/beam_selected_region.lua +++ b/src/beam_selected_region.lua @@ -4,9 +4,10 @@ function plugindef() finaleplugin.RequireSelection = true finaleplugin.Author = "Robert Patterson" finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Version = "1.0.1" - finaleplugin.Date = "June 12, 2020" + finaleplugin.Version = "2.0" + finaleplugin.Date = "May 17, 2022" finaleplugin.CategoryTags = "Note" + finaleplugin.MinJWLuaVersion = 0.62 finaleplugin.Notes = [[ This script beams together any notes or rests in the selected region that can be beamed together and breaks beams that cross into or out of the selected @@ -17,8 +18,22 @@ function plugindef() It does *not* create beams over barlines. + By default, the plugin installs two menu options, one to beam the selected region and + the other to unbeam the selected region. You can instead unbeam all notes in the selected region + by invoking the "Beam Selected Region" menu option with the Option key pressed (macOS) or + the Shift key pressed. This is identical to invoking the "Unbeam Selected Region" menu option. + This script could be particularly useful if you assign it a keystroke using a keyboard macro utility. ]] + finaleplugin.AdditionalMenuOptions = [[ + Unbeam Selected Region + ]] + finaleplugin.AdditionalDescriptions = [[ + Unbeam Selected Region + ]] + finaleplugin.AdditionalPrefixes = [[ + do_unbeam = true + ]] return "Beam Selected Region", "Beam Selected Region", "Beam Selected Region" end @@ -42,8 +57,10 @@ function beam_selected_region() if not isV2 then first_in_beam_v2 = true end - if entry:GetDuration() < 1024 then -- less than quarter note duration - if (not isV2 and first_in_beam) or (isV2 and first_in_beam_v2) then + if entry:GetDuration() < finale.QUARTER_NOTE then -- less than quarter note duration + if do_unbeam then + entry:SetBeamBeat(true) + elseif (not isV2 and first_in_beam) or (isV2 and first_in_beam_v2) then entry:SetBeamBeat(true) if not isV2 then first_in_beam = false @@ -54,12 +71,14 @@ function beam_selected_region() entry:SetBeamBeat(false) end local next_entry = note_entry.get_next_same_v(entry) - if (nil ~= next_entry) and (next_entry:GetDuration() < 1024) and - not finenv.Region():IsEntryPosWithin(next_entry) then + if (nil ~= next_entry) and (next_entry:GetDuration() < finale.QUARTER_NOTE) and not finenv.Region():IsEntryPosWithin(next_entry) then next_entry:SetBeamBeat(true) end end end end +if do_unbeam == nil then + do_unbeam = finenv.QueryInvokedModifierKeys(finale.CMDMODKEY_ALT) or finenv.QueryInvokedModifierKeys(finale.CMDMODKEY_SHIFT) +end beam_selected_region() diff --git a/src/beam_unbeam_selected_region.lua b/src/beam_unbeam_selected_region.lua deleted file mode 100644 index 7c14c6b2..00000000 --- a/src/beam_unbeam_selected_region.lua +++ /dev/null @@ -1,29 +0,0 @@ -function plugindef() - -- This function and the 'finaleplugin' namespace - -- are both reserved for the plug-in definition. - finaleplugin.RequireSelection = true - finaleplugin.Author = "Robert Patterson" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Version = "1.0.1" - finaleplugin.Date = "June 12, 2020" - finaleplugin.CategoryTags = "Note" - return "Unbeam Selected Region", "Unbeam Selected Region", "Unbeam Selected Region" -end - -local note_entry = require("library.note_entry") - -function unbeam_selected_region() - for entry in eachentrysaved(finenv.Region()) do - local isV2 = entry:GetVoice2() - if entry:GetDuration() < 1024 then -- less than quarter note duration - entry:SetBeamBeat(true) - end - local next_entry = note_entry.get_next_same_v(entry) - if (nil ~= next_entry) and (next_entry:GetDuration() < 1024) and - not finenv.Region():IsEntryPosWithin(next_entry) then - next_entry:SetBeamBeat(true) - end - end -end - -unbeam_selected_region() diff --git a/src/chord_baseline_move_down.lua b/src/chord_baseline_move_down.lua deleted file mode 100644 index c629bea8..00000000 --- a/src/chord_baseline_move_down.lua +++ /dev/null @@ -1,41 +0,0 @@ -function plugindef() - finaleplugin.Author = "Nick Mazuk" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Version = "1.0.0" - finaleplugin.Date = "March 26, 2022" - finaleplugin.CategoryTags = "Chord" - finaleplugin.AuthorURL = "https://nickmazuk.com" - finaleplugin.Notes = [[ - Select system staves for which you want to move the chord baseline down, then run this script - ]] - return "Move chord baseline down", "Move chord baseline down", "Moves the selected chord baseline down one space" -end - -local measurement = require("library.measurement") - -function chord_baseline_move_down() - local region = finenv.Region() - local systems = finale.FCStaffSystems() - systems:LoadAll() - - local start_measure = region:GetStartMeasure() - local end_measure = region:GetEndMeasure() - local system = systems:FindMeasureNumber(start_measure) - local last_system = systems:FindMeasureNumber(end_measure) - local system_number = system:GetItemNo() - local last_system_number = last_system:GetItemNo() - local start_staff = region:GetStartStaff() - local end_staff = region:GetEndStaff() - - for i = system_number, last_system_number, 1 do - local baselines = finale.FCBaselines() - baselines:LoadAllForSystem(finale.BASELINEMODE_CHORD, i) - for j = start_staff, end_staff, 1 do - local baseline = baselines:AssureSavedStaff(finale.BASELINEMODE_CHORD, i, j) - baseline.VerticalOffset = baseline.VerticalOffset + measurement.convert_to_EVPUs("-1s") - baseline:Save() - end - end -end - -chord_baseline_move_down() diff --git a/src/chord_baseline_move_up.lua b/src/chord_baseline_move_up.lua deleted file mode 100644 index 0ff225e6..00000000 --- a/src/chord_baseline_move_up.lua +++ /dev/null @@ -1,41 +0,0 @@ -function plugindef() - finaleplugin.Author = "Nick Mazuk" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Version = "1.0.0" - finaleplugin.Date = "March 26, 2022" - finaleplugin.CategoryTags = "Chord" - finaleplugin.AuthorURL = "https://nickmazuk.com" - finaleplugin.Notes = [[ - Select system staves for which you want to move the chord baseline up, then run this script - ]] - return "Move chord baseline up", "Move chord baseline up", "Moves the selected chord baseline up one space" -end - -local measurement = require("library.measurement") - -function chord_baseline_move_down() - local region = finenv.Region() - local systems = finale.FCStaffSystems() - systems:LoadAll() - - local start_measure = region:GetStartMeasure() - local end_measure = region:GetEndMeasure() - local system = systems:FindMeasureNumber(start_measure) - local last_system = systems:FindMeasureNumber(end_measure) - local system_number = system:GetItemNo() - local last_system_number = last_system:GetItemNo() - local start_staff = region:GetStartStaff() - local end_staff = region:GetEndStaff() - - for i = system_number, last_system_number, 1 do - local baselines = finale.FCBaselines() - baselines:LoadAllForSystem(finale.BASELINEMODE_CHORD, i) - for j = start_staff, end_staff, 1 do - local baseline = baselines:AssureSavedStaff(finale.BASELINEMODE_CHORD, i, j) - baseline.VerticalOffset = baseline.VerticalOffset + measurement.convert_to_EVPUs("1s") - baseline:Save() - end - end -end - -chord_baseline_move_down() diff --git a/src/expression_baseline_below_move_down.lua b/src/expression_baseline_below_move_down.lua deleted file mode 100644 index d2a3a6a8..00000000 --- a/src/expression_baseline_below_move_down.lua +++ /dev/null @@ -1,40 +0,0 @@ -function plugindef() - finaleplugin.RequireSelection = true - finaleplugin.Author = "Robert Patterson" - finaleplugin.Version = "1.0" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Date = "July 7, 2021" - finaleplugin.CategoryTags = "Expression" - finaleplugin.AuthorURL = "http://robertgpatterson.com" - return "Move Expression Baseline Below Down", "Move Expression Baseline Below Down", - "Moves the selected expression above baseline down one space" -end - -local measurement = require("library.measurement") - -function expression_baseline_below_move_down() - local region = finenv.Region() - local systems = finale.FCStaffSystems() - systems:LoadAll() - - local start_measure = region:GetStartMeasure() - local end_measure = region:GetEndMeasure() - local system = systems:FindMeasureNumber(start_measure) - local lastSys = systems:FindMeasureNumber(end_measure) - local system_number = system:GetItemNo() - local lastSys_number = lastSys:GetItemNo() - local start_slot = region:GetStartSlot() - local end_slot = region:GetEndSlot() - - for i = system_number, lastSys_number, 1 do - local baselines = finale.FCBaselines() - baselines:LoadAllForSystem(finale.BASELINEMODE_EXPRESSIONBELOW, i) - for j = start_slot, end_slot do - bl = baselines:AssureSavedStaff(finale.BASELINEMODE_EXPRESSIONBELOW, i, region:CalcStaffNumber(j)) - bl.VerticalOffset = bl.VerticalOffset - measurement.convert_to_EVPUs("1s") - bl:Save() - end - end -end - -expression_baseline_below_move_down() diff --git a/src/expression_baseline_below_move_up.lua b/src/expression_baseline_below_move_up.lua deleted file mode 100644 index b90dbff0..00000000 --- a/src/expression_baseline_below_move_up.lua +++ /dev/null @@ -1,40 +0,0 @@ -function plugindef() - finaleplugin.RequireSelection = true - finaleplugin.Author = "Robert Patterson" - finaleplugin.Version = "1.0" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Date = "July 7, 2021" - finaleplugin.CategoryTags = "Expression" - finaleplugin.AuthorURL = "http://robertgpatterson.com" - return "Move Expression Baseline Below Up", "Move Expression Baseline Below Up", - "Moves the selected expression below baseline up one space" -end - -local measurement = require("library.measurement") - -function expression_baseline_below_move_up() - local region = finenv.Region() - local systems = finale.FCStaffSystems() - systems:LoadAll() - - local start_measure = region:GetStartMeasure() - local end_measure = region:GetEndMeasure() - local system = systems:FindMeasureNumber(start_measure) - local lastSys = systems:FindMeasureNumber(end_measure) - local system_number = system:GetItemNo() - local lastSys_number = lastSys:GetItemNo() - local start_slot = region:GetStartSlot() - local end_slot = region:GetEndSlot() - - for i = system_number, lastSys_number, 1 do - local baselines = finale.FCBaselines() - baselines:LoadAllForSystem(finale.BASELINEMODE_EXPRESSIONBELOW, i) - for j = start_slot, end_slot do - bl = baselines:AssureSavedStaff(finale.BASELINEMODE_EXPRESSIONBELOW, i, region:CalcStaffNumber(j)) - bl.VerticalOffset = bl.VerticalOffset + measurement.convert_to_EVPUs("1s") - bl:Save() - end - end -end - -expression_baseline_below_move_up() diff --git a/src/expression_baseline_below_reset.lua b/src/expression_baseline_below_reset.lua deleted file mode 100644 index 6a6ac321..00000000 --- a/src/expression_baseline_below_reset.lua +++ /dev/null @@ -1,39 +0,0 @@ -function plugindef() - finaleplugin.RequireSelection = true - finaleplugin.Author = "Robert Patterson" - finaleplugin.Version = "1.0" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Date = "July 7, 2021" - finaleplugin.CategoryTags = "Expression" - finaleplugin.AuthorURL = "http://robertgpatterson.com" - return "Reset Expression Baseline Below", "Reset Expression Baseline Below", - "Resets the selected expression below baselines" -end - -function expression_baseline_below_reset() - local region = finenv.Region() - local systems = finale.FCStaffSystems() - systems:LoadAll() - - local start_measure = region:GetStartMeasure() - local end_measure = region:GetEndMeasure() - local system = systems:FindMeasureNumber(start_measure) - local lastSys = systems:FindMeasureNumber(end_measure) - local system_number = system:GetItemNo() - local lastSys_number = lastSys:GetItemNo() - local start_slot = region:GetStartSlot() - local end_slot = region:GetEndSlot() - - for i = system_number, lastSys_number, 1 do - local baselines = finale.FCBaselines() - baselines:LoadAllForSystem(finale.BASELINEMODE_EXPRESSIONBELOW, i) - for baseline in each(baselines) do - local baseline_slot = region:CalcSlotNumber(baseline.Staff) - if (start_slot <= baseline_slot) and (baseline_slot <= end_slot) then - baseline:DeleteData() - end - end -end -end - -expression_baseline_below_reset() diff --git a/src/expression_baseline_move_down.lua b/src/expression_baseline_move_down.lua deleted file mode 100644 index 100d45b2..00000000 --- a/src/expression_baseline_move_down.lua +++ /dev/null @@ -1,40 +0,0 @@ -function plugindef() - finaleplugin.RequireSelection = true - finaleplugin.Author = "Nick Mazuk" - finaleplugin.Version = "1.0.2" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Date = "June 12, 2020" - finaleplugin.CategoryTags = "Expression" - finaleplugin.AuthorURL = "https://nickmazuk.com" - return "Move Expression Baseline Down", "Move Expression Baseline Down", - "Moves the selected expression above baseline down one space" -end - -local measurement = require("library.measurement") - -function expression_baseline_move_down() - local region = finenv.Region() - local systems = finale.FCStaffSystems() - systems:LoadAll() - - local start_measure = region:GetStartMeasure() - local end_measure = region:GetEndMeasure() - local system = systems:FindMeasureNumber(start_measure) - local lastSys = systems:FindMeasureNumber(end_measure) - local system_number = system:GetItemNo() - local lastSys_number = lastSys:GetItemNo() - local start_slot = region:GetStartSlot() - local end_slot = region:GetEndSlot() - - for i = system_number, lastSys_number, 1 do - local baselines = finale.FCBaselines() - baselines:LoadAllForSystem(finale.BASELINEMODE_EXPRESSIONABOVE, i) - for j = start_slot, end_slot do - bl = baselines:AssureSavedStaff(finale.BASELINEMODE_EXPRESSIONABOVE, i, region:CalcStaffNumber(j)) - bl.VerticalOffset = bl.VerticalOffset - measurement.convert_to_EVPUs("1s") - bl:Save() - end - end -end - -expression_baseline_move_down() diff --git a/src/expression_baseline_move_up.lua b/src/expression_baseline_move_up.lua deleted file mode 100644 index df5878b7..00000000 --- a/src/expression_baseline_move_up.lua +++ /dev/null @@ -1,40 +0,0 @@ -function plugindef() - finaleplugin.RequireSelection = true - finaleplugin.Author = "Nick Mazuk" - finaleplugin.Version = "1.0.2" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Date = "June 12, 2020" - finaleplugin.CategoryTags = "Expression" - finaleplugin.AuthorURL = "https://nickmazuk.com" - return "Move Expression Baseline Up", "Move Expression Baseline Up", - "Moves the selected expression above baseline up one space" -end - -local measurement = require("library.measurement") - -function expression_baseline_move_up() - local region = finenv.Region() - local systems = finale.FCStaffSystems() - systems:LoadAll() - - local start_measure = region:GetStartMeasure() - local end_measure = region:GetEndMeasure() - local system = systems:FindMeasureNumber(start_measure) - local lastSys = systems:FindMeasureNumber(end_measure) - local system_number = system:GetItemNo() - local lastSys_number = lastSys:GetItemNo() - local start_slot = region:GetStartSlot() - local end_slot = region:GetEndSlot() - - for i = system_number, lastSys_number, 1 do - local baselines = finale.FCBaselines() - baselines:LoadAllForSystem(finale.BASELINEMODE_EXPRESSIONABOVE, i) - for j = start_slot, end_slot do - bl = baselines:AssureSavedStaff(finale.BASELINEMODE_EXPRESSIONABOVE, i, region:CalcStaffNumber(j)) - bl.VerticalOffset = bl.VerticalOffset + measurement.convert_to_EVPUs("1s") - bl:Save() - end - end -end - -expression_baseline_move_up() diff --git a/src/expression_baseline_reset.lua b/src/expression_baseline_reset.lua deleted file mode 100644 index 300eb558..00000000 --- a/src/expression_baseline_reset.lua +++ /dev/null @@ -1,39 +0,0 @@ -function plugindef() - finaleplugin.RequireSelection = true - finaleplugin.Author = "Robert Patterson" - finaleplugin.Version = "1.0" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Date = "July 7, 2021" - finaleplugin.CategoryTags = "Expression" - finaleplugin.AuthorURL = "http://robertgpatterson.com" - return "Reset Expression Baseline", "Reset Expression Baseline", - "Resets the selected expression above baselines" -end - -function expression_baseline_reset() - local region = finenv.Region() - local systems = finale.FCStaffSystems() - systems:LoadAll() - - local start_measure = region:GetStartMeasure() - local end_measure = region:GetEndMeasure() - local system = systems:FindMeasureNumber(start_measure) - local lastSys = systems:FindMeasureNumber(end_measure) - local system_number = system:GetItemNo() - local lastSys_number = lastSys:GetItemNo() - local start_slot = region:GetStartSlot() - local end_slot = region:GetEndSlot() - - for i = system_number, lastSys_number, 1 do - local baselines = finale.FCBaselines() - baselines:LoadAllForSystem(finale.BASELINEMODE_EXPRESSIONABOVE, i) - for baseline in each(baselines) do - local baseline_slot = region:CalcSlotNumber(baseline.Staff) - if (start_slot <= baseline_slot) and (baseline_slot <= end_slot) then - baseline:DeleteData() - end - end -end -end - -expression_baseline_reset() diff --git a/src/library/note_entry.lua b/src/library/note_entry.lua index a9c0def9..4a461b1a 100644 --- a/src/library/note_entry.lua +++ b/src/library/note_entry.lua @@ -318,7 +318,10 @@ function note_entry.delete_note(note) finale.FCNoteheadMod():EraseAt(note) finale.FCPercussionNoteMod():EraseAt(note) finale.FCTablatureNoteMod():EraseAt(note) - -- finale.FCTieMod():EraseAt(note) -- FCTieMod is not currently lua supported, but leave this here in case it ever is + if finale.FCTieMod then -- added in RGP Lua 0.62 + finale.FCTieMod(finale.TIEMODTYPE_TIESTART):EraseAt(note) + finale.FCTieMod(finale.TIEMODTYPE_TIEEND):EraseAt(note) + end return entry:DeleteNote(note) end diff --git a/src/library/tie.lua b/src/library/tie.lua new file mode 100644 index 00000000..70aa1ee3 --- /dev/null +++ b/src/library/tie.lua @@ -0,0 +1,807 @@ +--[[ +$module Tie + +This library encapsulates Finale's behavior for initializing FCTieMod endpoints, +as well as providing other useful information about ties. +]] -- +local tie = {} + +-- returns the equal note in the next closest entry or nil if none +local equal_note = function(entry, target_note, for_tieend) + if entry:IsRest() then + return nil + end + -- By using CalcStaffPosition we can support a key change in the middle of a tie. But it is at the cost + -- of not supporting a clef change in the middle of the tie. A calculation comparing normalized concert + -- pitch is *much* more complicated code, and clef changes in the middle of ties seem like a very limited + -- use case. + local target_staffline = target_note:CalcStaffPosition() + for note in each(entry) do + local this_staffline = note:CalcStaffPosition() + if this_staffline == target_staffline then + if for_tieend then + if note.TieBackwards then + return note + end + else + if note.Tie then + return note + end + end + end + end + return nil +end + +-- returns the note that the input note is tied to. +-- for this function to work, note must be from a FCNoteEntryLayer +-- instance constructed by function tie_span. +local function tied_to(note) + if not note then + return nil + end + local next_entry = note.Entry + if next_entry then + next_entry = next_entry:Next() + if next_entry and not next_entry.GraceNote then + local tied_to_note = equal_note(next_entry, note, true) + if tied_to_note then + return tied_to_note + end + if next_entry.Voice2Launch then + local next_v2_entry = next_entry:Next() + tied_to_note = equal_note(next_v2_entry, note, true) + if tied_to_note then + return tied_to_note + end + end + end + end + return nil +end + +-- returns the note that the input note is tied from. +-- for this function to work, note must be from a FCNoteEntryLayer +-- instance constructed by function tie_span. +local function tied_from(note) + if not note then + return nil + end + local entry = note.Entry + while true do + entry = entry:Previous() + if not entry then + break + end + tied_from_note = equal_note(entry, note, false) + if tied_from_note then + return tied_from_note + end + end +end + +-- returns FCNoteEntryLayer, along with start and end FCNotes for the tie that +-- are contained within the FCNoteEntryLayer. +local tie_span = function(note, for_tieend) + local start_measnum = (for_tieend and note.Entry.Measure > 1) and note.Entry.Measure - 1 or note.Entry.Measure + local end_measnum = for_tieend and note.Entry.Measure or note.Entry.Measure + 1 + local note_entry_layer = finale.FCNoteEntryLayer(note.Entry.LayerNumber - 1, note.Entry.Staff, start_measnum, end_measnum) + note_entry_layer:Load() + local same_entry + for entry in each(note_entry_layer) do + if entry.EntryNumber == note.Entry.EntryNumber then + same_entry = entry + break + end + end + if not same_entry then + return note_entry_layer + end + local note_entry_layer_note = same_entry:GetItemAt(note.NoteIndex) + local start_note = for_tieend and tied_from(note_entry_layer_note) or note_entry_layer_note + local end_note = for_tieend and note_entry_layer_note or tied_to(note_entry_layer_note) + return note_entry_layer, start_note, end_note +end + +--[[ +% calc_default_direction + +Calculates the default direction of a tie based on context and FCTiePrefs but ignoring multi-voice +and multi-layer overrides. It also does not take into account the direction being overridden in +FCTieMods. Use tie.calc_direction to calculate the actual current tie direction. + +@ note (FCNote) the note for which to return the tie direction. +@ for_tieend (boolean) specifies that this request is for a tie_end. +@ [tie_prefs] (FCTiePrefs) use these tie prefs if supplied +: (number) Returns either TIEMODDIR_UNDER or TIEMODDIR_OVER. If the input note has no applicable tie, it returns 0. +]] +function tie.calc_default_direction(note, for_tieend, tie_prefs) + if for_tieend then + if not note.TieBackwards then + return 0 + end + else + if not note.Tie then + return 0 + end + end + if not tie_prefs then + tie_prefs = finale.FCTiePrefs() + tie_prefs:Load(0) + end + local stemdir = note.Entry:CalcStemUp() and 1 or -1 + if note.Entry.Count > 1 then + -- This code depends on observed Finale behavior that the notes are always sorted + -- from lowest-to-highest inside the entry. If Finale's behavior ever changes, this + -- code is screwed. + + -- If note is outer, tie-direction is unaffected by tie_prefs + if note.NoteIndex == 0 then + return finale.TIEMODDIR_UNDER + end + if note.NoteIndex == note.Entry.Count - 1 then + return finale.TIEMODDIR_OVER + end + + local inner_default = 0 + + if tie_prefs.ChordDirectionType ~= finale.TIECHORDDIR_STEMREVERSAL then + if note.NoteIndex < math.floor(note.Entry.Count / 2) then + inner_default = finale.TIEMODDIR_UNDER + end + if note.NoteIndex >= math.floor((note.Entry.Count + 1) / 2) then + inner_default = finale.TIEMODDIR_OVER + end + if tie_prefs.ChordDirectionType == finale.TIECHORDDIR_OUTSIDEINSIDE then + inner_default = (stemdir > 0) and finale.TIEMODDIR_UNDER or finale.TIEMODDIR_OVER + end + end + if inner_default == 0 or tie_prefs.ChordDirectionType == finale.TIECHORDDIR_STEMREVERSAL then + local staff_position = note:CalcStaffPosition() + local curr_staff = finale.FCCurrentStaffSpec() + curr_staff:LoadForEntry(note.Entry) + inner_default = staff_position < curr_staff.StemReversalPosition and finale.TIEMODDIR_UNDER or finale.TIEMODDIR_OVER + end + if inner_default ~= 0 then + if tie_prefs.ChordDirectionOpposingSeconds then + if inner_default == finale.TIEMODDIR_OVER and not note:IsUpper2nd() and note:IsLower2nd() then + return finale.TIEMODDIR_UNDER + end + if inner_default == finale.TIEMODDIR_UNDER and note:IsUpper2nd() and not note:IsLower2nd() then + return finale.TIEMODDIR_OVER + end + end + return inner_default + end + else + local adjacent_stemdir = 0 + local note_entry_layer, start_note, end_note = tie_span(note, for_tieend) + if for_tieend then + -- There seems to be a "bug" in how Finale determines mixed-stem values for Tie-Ends. + -- It looks at the stem direction of the immediately preceding entry, even if that entry + -- is not the entry that started the tie. Therefore, do not use tied_from_note to + -- get the stem direction. + if end_note then + local start_entry = end_note.Entry:Previous() + if start_entry then + adjacent_stemdir = start_entry:CalcStemUp() and 1 or -1 + end + end + else + if end_note then + adjacent_stemdir = end_note.Entry:CalcStemUp() and 1 or -1 + end + if adjacent_stemdir == 0 and start_note then + -- Finale (as of v2K) has the following Mickey Mouse behavior. When no Tie-To note exists, + -- it determines the mixed stem value based on + -- 1. If the next entry is a rest, the adjStemDir is indeterminate so use stemDir (i.e., fall thru to bottom) + -- 2. If the next entry is a note with its stem frozen, use it + -- 3. If the next entry floats, but it has a V2Launch, then if EITHER the V1 or + -- the V2 has a stem in the opposite direction, use it. + local next_entry = start_note.Entry:Next() + if next_entry and not next_entry:IsRest() then + adjacent_stemdir = next_entry:CalcStemUp() and 1 or -1 + if not next_entry.FreezeStem and next_entry.Voice2Launch and adjacent_stemdir == stemdir then + next_entry = next_entry:Next() + if next_entry then + adjacent_stemdir = next_entry:CalcStemUp() and 1 or -1 + end + end + end + end + if adjacent_stemdir ~= 0 and adjacent_stemdir ~= stemdir then + if tie_prefs.MixedStemDirectionType == finale.TIEMIXEDSTEM_OVER then + return finale.TIEMODDIR_OVER + elseif tie_prefs.MixedStemDirectionType == finale.TIEMIXEDSTEM_UNDER then + return finale.TIEMODDIR_UNDER + end + end + end + end + + return (stemdir > 0) and finale.TIEMODDIR_UNDER or finale.TIEMODDIR_OVER + +end -- function tie.default_direction + +local calc_layer_is_visible = function(staff, layer_number) + local altnotation_layer = staff.AltNotationLayer + if layer_number ~= altnotation_layer then + return staff.AltShowOtherNotes + end + + local hider_altnotation_types = { + finale.ALTSTAFF_BLANKNOTATION, finale.ALTSTAFF_SLASHBEATS, finale.ALTSTAFF_ONEBARREPEAT, finale.ALTSTAFF_TWOBARREPEAT, finale.ALTSTAFF_BLANKNOTATIONRESTS, + } + local altnotation_type = staff.AltNotationStyle + for _, v in pairs(hider_altnotation_types) do + if v == altnotation_type then + return false + end + end + + return true +end + +local calc_other_layers_visible = function(entry) + local staff = finale.FCCurrentStaffSpec() + staff:LoadForEntry(entry) + for layer = 1, finale.FCLayerPrefs.GetMaxLayers() do + if layer ~= entry.LayerNumber and calc_layer_is_visible(staff, layer) then + local layer_prefs = finale.FCLayerPrefs() + if layer_prefs:Load(layer - 1) and not layer_prefs.HideWhenInactive then + local layer_entries = finale.FCNoteEntryLayer(layer - 1, entry.Staff, entry.Measure, entry.Measure) + if layer_entries:Load() then + for layer_entry in each(layer_entries) do + if layer_entry.Visible then + return true + end + end + end + end + end + end + return false +end + +local layer_stem_direction = function(layer_prefs, entry) + if layer_prefs.UseFreezeStemsTies then + if layer_prefs.UseRestOffsetInMultiple then -- UseRestOffsetInMultiple controls a lot more than just rests + if not entry:CalcMultiLayeredCell() then + return 0 + end + if layer_prefs.IgnoreHiddenNotes and not calc_other_layers_visible(entry) then + return 0 + end + end + return layer_prefs.FreezeStemsUp and 1 or -1 + end + return 0 +end + +local layer_tie_direction = function(entry) + local layer_prefs = finale.FCLayerPrefs() + if not layer_prefs:Load(entry.LayerNumber - 1) then + return 0 + end + local layer_stemdir = layer_stem_direction(layer_prefs, entry) + if layer_stemdir ~= 0 and layer_prefs.FreezeTiesSameDirection then + return layer_stemdir > 0 and finale.TIEMODDIR_OVER or finale.TIEMODDIR_UNDER + end + return 0 +end + +--[[ +% calc_direction + +Calculates the current direction of a tie based on context and FCTiePrefs, taking into account multi-voice +and multi-layer overrides. It also takes into account if the direction has been overridden in +FCTieMods. + +@ note (FCNote) the note for which to return the tie direction. +@ tie_mod (FCTieMod) the tie mods for the note, if any. +@ [tie_prefs] (FCTiePrefs) use these tie prefs if supplied +: (number) Returns either TIEMODDIR_UNDER or TIEMODDIR_OVER. If the input note has no applicable tie, it returns 0. +]] +function tie.calc_direction(note, tie_mod, tie_prefs) + -- much of this code works even if the note doesn't (yet) have a tie, so + -- skip the check to see if we actually have a tie. + if tie_mod.TieDirection ~= finale.TIEMODDIR_AUTOMATIC then + return tie_mod.TieDirection + end + if note.Entry.SplitStem then + return note.UpstemSplit and finale.TIEMODDIR_OVER or finale.TIEMODDIR_UNDER + end + local layer_tiedir = layer_tie_direction(note.Entry) + if layer_tiedir ~= 0 then + return layer_tiedir + end + if note.Entry.Voice2Launch or note.Entry.Voice2 then + return note.Entry:CalcStemUp() and finale.TIEMODDIR_OVER or finale.TIEMODDIR_UNDER + end + if note.Entry.FlipTie then + return note.Entry:CalcStemUp() and finale.TIEMODDIR_OVER or finale.TIEMODDIR_UNDER + end + + return tie.calc_default_direction(note, not tie_mod:IsStartTie(), tie_prefs) +end -- function tie.calc_direction + +local calc_is_end_of_system = function(note, for_pageview) + if not note.Entry:Next() then + local region = finale.FCMusicRegion() + region:SetFullDocument() + if note.Entry.Measure == region.EndMeasure then + return true + end + end + if for_pageview then + local note_entry_layer, start_note, end_note = tie_span(note, false) + if start_note and end_note then + local systems = finale.FCStaffSystems() + systems:LoadAll() + local start_system = systems:FindMeasureNumber(start_note.Entry.Measure) + local end_system = systems:FindMeasureNumber(end_note.Entry.Measure) + return start_system.ItemNo ~= end_system.ItemNo + end + end + return false +end + +local has_nonaligned_2nd = function(entry) + for note in each(entry) do + if note:IsNonAligned2nd() then + return true + end + end + return false +end + +--[[ +% calc_connection_code + +Calculates the correct connection code for activating a Tie Placement Start Point or End Point +in FCTieMod. + +@ note (FCNote) the note for which to return the code +@ placement (number) one of the TIEPLACEMENT_INDEXES values +@ direction (number) one of the TIEMOD_DIRECTION values +@ for_endpoint (boolean) if true, calculate the end point code, otherwise the start point code +@ for_tieend (boolean) if true, calculate the code for a tie end +@ for_pageview (boolean) if true, calculate the code for page view, otherwise for scroll/studio view +@ [tie_prefs] (FCTiePrefs) use these tie prefs if supplied +: (number) Returns one of TIEMOD_CONNECTION_CODES. If the input note has no applicable tie, it returns TIEMODCNCT_NONE. +]] +function tie.calc_connection_code(note, placement, direction, for_endpoint, for_tieend, for_pageview, tie_prefs) + -- As of now, I haven't found any use for the connection codes: + -- TIEMODCNCT_ENTRYCENTER_NOTEBOTTOM + -- TIEMODCNCT_ENTRYCENTER_NOTETOP + -- The other 15 are accounted for here. RGP 5/11/2022 + if not tie_prefs then + tie_prefs = finale.FCTiePrefs() + tie_prefs:Load(0) + end + if not for_endpoint and for_tieend then + return finale.TIEMODCNCT_SYSTEMSTART + end + if for_endpoint and not for_tieend and calc_is_end_of_system(note, for_pageview) then + return finale.TIEMODCNCT_SYSTEMEND + end + if placement == finale.TIEPLACE_OVERINNER or placement == finale.TIEPLACE_UNDERINNER then + local stemdir = note.Entry:CalcStemUp() and 1 or -1 + if for_endpoint then + if tie_prefs.BeforeSingleAccidental and note.Entry.Count == 1 and note:CalcAccidental() then + return finale.TIEMODCNCT_ACCILEFT_NOTECENTER + end + if has_nonaligned_2nd(note.Entry) then + if (stemdir > 0 and direction ~= finale.TIEMODDIR_UNDER and note:IsNonAligned2nd()) or (stemdir < 0 and not note:IsNonAligned2nd()) then + return finale.TIEMODCNCT_NOTELEFT_NOTECENTER + end + end + return finale.TIEMODCNCT_ENTRYLEFT_NOTECENTER + else + local num_dots = note.Entry:CalcDots() + if (tie_prefs.AfterSingleDot and num_dots == 1) or (tie_prefs.AfterMultipleDots and num_dots > 1) then + return finale.TIEMODCNCT_DOTRIGHT_NOTECENTER + end + if has_nonaligned_2nd(note.Entry) then + if (stemdir > 0 and not note:IsNonAligned2nd()) or (stemdir < 0 and direction ~= finale.TIEMODDIR_OVER and note:IsNonAligned2nd()) then + return finale.TIEMODCNCT_NOTERIGHT_NOTECENTER + end + end + return finale.TIEMODCNCT_ENTRYRIGHT_NOTECENTER + end + elseif placement == finale.TIEPLACE_OVEROUTERNOTE then + return finale.TIEMODCNCT_NOTECENTER_NOTETOP + elseif placement == finale.TIEPLACE_UNDEROUTERNOTE then + return finale.TIEMODCNCT_NOTECENTER_NOTEBOTTOM + elseif placement == finale.TIEPLACE_OVEROUTERSTEM then + return for_endpoint and finale.TIEMODCNCT_NOTELEFT_NOTETOP or finale.TIEMODCNCT_NOTERIGHT_NOTETOP + elseif placement == finale.TIEPLACE_UNDEROUTERSTEM then + return for_endpoint and finale.TIEMODCNCT_NOTELEFT_NOTEBOTTOM or finale.TIEMODCNCT_NOTERIGHT_NOTEBOTTOM + end + return finale.TIEMODCNCT_NONE +end + +local calc_placement_for_endpoint = function(note, tie_mod, tie_prefs, direction, stemdir, for_endpoint, end_note_slot, end_num_notes, end_upstem2nd, end_downstem2nd) + local note_slot = end_note_slot and end_note_slot or note.NoteIndex + local num_notes = end_num_notes and end_num_notes or note.Entry.Count + local upstem2nd = end_upstem2nd ~= nil and end_upstem2nd or note.Upstem2nd + local downstem2nd = end_downstem2nd ~= nil and end_downstem2nd or note.Downstem2nd + if (note_slot == 0 and direction == finale.TIEMODDIR_UNDER) or (note_slot == num_notes - 1 and direction == finale.TIEMODDIR_OVER) then + local use_outer = false + local manual_override = false + if tie_mod.OuterPlacement ~= finale.TIEMODSEL_DEFAULT then + manual_override = true + if tie_mod.OuterPlacement == finale.TIEMODSEL_ON then + use_outer = true + end + end + if not manual_override and tie_prefs.UseOuterPlacement then + use_outer = true + end + if use_outer then + if note.Entry.Duration < finale.WHOLE_NOTE then + if for_endpoint then + -- A downstem 2nd is always treated as OuterNote + -- An upstem 2nd is always treated as OuterStem + if stemdir < 0 and direction == finale.TIEMODDIR_UNDER and not downstem2nd then + return finale.TIEPLACE_UNDEROUTERSTEM + end + if stemdir > 0 and direction == finale.TIEMODDIR_OVER and upstem2nd then + return finale.TIEPLACE_OVEROUTERSTEM + end + else + -- see comments above and take their opposites + if stemdir > 0 and direction == finale.TIEMODDIR_OVER and not upstem2nd then + return finale.TIEPLACE_OVEROUTERSTEM + end + if stemdir < 0 and direction == finale.TIEMODDIR_UNDER and downstem2nd then + return finale.TIEPLACE_UNDEROUTERSTEM + end + end + end + return direction == finale.TIEMODDIR_UNDER and finale.TIEPLACE_UNDEROUTERNOTE or finale.TIEPLACE_OVEROUTERNOTE + end + end + return direction == finale.TIEMODDIR_UNDER and finale.TIEPLACE_UNDERINNER or finale.TIEPLACE_OVERINNER +end + +--[[ +% calc_placement + +Calculates the current placement of a tie based on context and FCTiePrefs. + +@ note (FCNote) the note for which to return the tie direction. +@ tie_mod (FCTieMod) the tie mods for the note, if any. +@ for_pageview (bool) true if calculating for Page View, false for Scroll/Studio View +@ direction (number) one of the TIEMOD_DIRECTION values or nil (if you don't know it yet) +@ [tie_prefs] (FCTiePrefs) use these tie prefs if supplied +: (number) TIEPLACEMENT_INDEXES value for start point +: (number) TIEPLACEMENT_INDEXES value for end point +]] +function tie.calc_placement(note, tie_mod, for_pageview, direction, tie_prefs) + if not tie_prefs then + tie_prefs = finale.FCTiePrefs() + tie_prefs:Load(0) + end + direction = direction and direction ~= finale.TIEMODDIR_AUTOMATIC and direction or tie.calc_direction(note, tie_mod, tie_prefs) + local stemdir = note.Entry:CalcStemUp() and 1 or -1 + local start_placement, end_placement + if not tie_mod:IsStartTie() then + start_placement = calc_placement_for_endpoint(note, tie_mod, tie_prefs, direction, stemdir, false) + end_placement = calc_placement_for_endpoint(note, tie_mod, tie_prefs, direction, stemdir, true) + else + start_placement = calc_placement_for_endpoint(note, tie_mod, tie_prefs, direction, stemdir, false) + end_placement = start_placement -- initialize it with something + local note_entry_layer, start_note, end_note = tie_span(note, false) + if end_note then + local next_stemdir = end_note.Entry:CalcStemUp() and 1 or -1 + end_placement = calc_placement_for_endpoint(end_note, tie_mod, tie_prefs, direction, next_stemdir, true) + else + -- more reverse-engineered logic. Here is the observed Finale behavior: + -- 1. Ties to rests and nothing have StemOuter placement at their endpoint. + -- 2. Ties to an adjacent empty bar have inner placement on both ends. (weird but true) + -- 3. Ties to notes are Inner if the down-tied-to entry has a note that is lower or + -- an up-tied-to entry has a note that is higher. + -- The flakiest behavior is with with under-ties to downstem chords containing 2nds. + -- In this case, we must pass in the UPSTEM 2nd bit or'ed from all notes in the chord. + local next_entry = start_note.Entry:Next() -- start_note is from note_entry_layer, which includes the next bar + if next_entry then + if not next_entry:IsRest() and next_entry.Count > 0 then + if direction == finale.TIEMODDIR_UNDER then + local next_note = next_entry:GetItemAt(0) + if next_note.Displacment < note.Displacement then + end_placement = finale.TIEPLACE_UNDERINNER + else + local next_stemdir = next_entry:CalcStemUp() and 1 or -1 + end_placement = calc_placement_for_endpoint(next_note, tie_mod, tie_prefs, direction, next_stemdir, true) + end + else + local next_note = next_entry:GetItemAt(next_entry.Count - 1) + if next_note.Displacment > note.Displacement then + end_placement = finale.TIEPLACE_OVERINNER + else + -- flaky behavior alert: this code might not work in a future release but + -- so far it it has held up. This is the Finale 2000 behavior. + -- If the entry is downstem, OR together all the Upstem 2nd bits. + -- Finale is so flaky that it does not do this for Scroll View at less than 130%. + -- However, it seems to do it consistently in Page View. + local upstem2nd = next_note.Upstem2nd + if next_entry:CalcStemUp() then + for check_note in each(next_entry) do + if check_note.Upstem2nd then + upstem2nd = true + end + end + local next_stemdir = direction == finale.TIEMODDIR_UNDER and -1 or 1 + end_placement = calc_placement_for_endpoint( + next_note, tie_mod, tie_prefs, direction, next_stemdir, true, next_note.NoteIndex, next_entry.Count, upstem2nd, + next_note.Downstem2nd) + end + end + end + else + local next_stemdir = direction == finale.TIEMODDIR_UNDER and -1 or 1 + end_placement = calc_placement_for_endpoint(note, tie_mod, tie_prefs, direction, next_stemdir, true, note.NoteIndex, note.Entry.Count, false, false) + end + else + if calc_is_end_of_system(note, for_pageview) then + end_placement = direction == finale.TIEMODDIR_UNDER and finale.TIEPLACE_UNDEROUTERSTEM or finale.TIEPLACE_OVEROUTERSTEM + else + end_placement = direction == finale.TIEMODDIR_UNDER and finale.TIEPLACE_UNDERINNER or finale.TIEPLACE_OVERINNER + end + end + end + end + + -- if either of the endpoints is inner, make both of them inner. + if start_placement == finale.TIEPLACE_OVERINNER or start_placement == finale.TIEPLACE_UNDERINNER then + end_placement = start_placement + elseif end_placement == finale.TIEPLACE_OVERINNER or end_placement == finale.TIEPLACE_UNDERINNER then + start_placement = end_placement + end + + return start_placement, end_placement +end + +local calc_prefs_offset_for_endpoint = function(note, tie_prefs, tie_placement_prefs, placement, for_endpoint, for_tieend, for_pageview) + local tie_ + if for_endpoint then + if calc_is_end_of_system(note, for_pageview) then + return tie_prefs.SystemRightHorizontalOffset, tie_placement_prefs:GetVerticalEnd(placement) + end + return tie_placement_prefs:GetHorizontalEnd(placement), tie_placement_prefs:GetVerticalEnd(placement) + end + if for_tieend then + return tie_prefs.SystemLeftHorizontalOffset, tie_placement_prefs:GetVerticalStart(placement) + end + return tie_placement_prefs:GetHorizontalStart(placement), tie_placement_prefs:GetVerticalStart(placement) +end + +local activate_endpoint = function(note, tie_mod, placement, direction, for_endpoint, for_pageview, tie_prefs, tie_placement_prefs) + local active_check_func = for_endpoint and tie_mod.IsEndPointActive or tie_mod.IsStartPointActive + if active_check_func(tie_mod) then + return false + end + local for_tieend = not tie_mod:IsStartTie() + local connect = tie.calc_connection_code(note, placement, direction, for_endpoint, for_tieend, for_pageview, tie_prefs) + local xoffset, yoffset = calc_prefs_offset_for_endpoint(note, tie_prefs, tie_placement_prefs, placement, for_endpoint, for_tieend, for_pageview) + local activation_func = for_endpoint and tie_mod.ActivateEndPoint or tie_mod.ActivateStartPoint + activation_func(tie_mod, direction == finale.TIEMODDIR_OVER, connect, xoffset, yoffset) + return true +end + +--[[ +% activate_endpoints + +Activates the placement endpoints of the input tie_mod and initializes them with their +default values. If an endpoint is already activated, that endpoint is not touched. + +@ note (FCNote) the note for which to return the tie direction. +@ tie_mod (FCTieMod) the tie mods for the note, if any. +@ for_pageview (bool) true if calculating for Page View, false for Scroll/Studio View +@ [tie_prefs] (FCTiePrefs) use these tie prefs if supplied +: (boolean) returns true if anything changed +]] +function tie.activate_endpoints(note, tie_mod, for_pageview, tie_prefs) + if not tie_prefs then + tie_prefs = finale.FCTiePrefs() + tie_prefs:Load(0) + end + local tie_placement_prefs = tie_prefs:CreateTiePlacementPrefs() + local direction = tie.calc_direction(note, tie_mod, tie_prefs) + local lplacement, rplacement = tie.calc_placement(note, tie_mod, for_pageview, direction, tie_prefs) + local lactivated = activate_endpoint(note, tie_mod, lplacement, direction, false, for_pageview, tie_prefs, tie_placement_prefs) + local ractivated = activate_endpoint(note, tie_mod, rplacement, direction, true, for_pageview, tie_prefs, tie_placement_prefs) + if lactivated and ractivated then + tie_mod:LocalizeFromPreferences() + end + return lactivated or ractivated +end + +local calc_tie_length = function(note, tie_mod, for_pageview, direction, tie_prefs, tie_placement_prefs) + local cell_metrics_start = finale.FCCellMetrics() + local entry_metrics_start = finale.FCEntryMetrics() + cell_metrics_start:LoadAtEntry(note.Entry) + entry_metrics_start:Load(note.Entry) + + local cell_metrics_end = finale.FCCellMetrics() + local entry_metrics_end = finale.FCEntryMetrics() + local note_entry_layer, start_note, end_note = tie_span(note, false) + if tie_mod:IsStartTie() then + if end_note then + cell_metrics_end:LoadAtEntry(end_note.Entry) + entry_metrics_end:Load(end_note.Entry) + end + end + + local lplacement, rplacement = tie.calc_placement(note, tie_mod, for_pageview, direction, tie_prefs) + local horz_start = 0 + local horz_end = 0 + local incr_start = 0 + local incr_end = 0 + + -- the following default locations are empirically determined. + local OUTER_NOTE_OFFSET_PCTG = 7.0 / 16.0 + local INNER_INCREMENT = 6 + + local staff_scaling = cell_metrics_start.StaffScaling / 10000.0 + local horz_stretch = for_pageview and 1 or cell_metrics_start.HorizontalStretch / 10000.0 + + if tie_mod:IsStartTie() then + horz_start = entry_metrics_start:GetNoteLeftPosition(note.NoteIndex) / horz_stretch + if lplacement == finale.TIEPLACE_OVERINNER or lplacement == finale.TIEPLACE_OVEROUTERSTEM or lplacement == finale.TIEPLACE_UNDERINNER then + horz_start = horz_start + entry_metrics_start:GetNoteWidth(note.NoteIndex) + incr_start = INNER_INCREMENT + else + horz_start = horz_start + (entry_metrics_start:GetNoteWidth(note.NoteIndex) * OUTER_NOTE_OFFSET_PCTG) + end + else + horz_start = (cell_metrics_start.MusicStartPos * staff_scaling) / horz_stretch + end + + if tie_mod:IsStartTie() and (not end_note or cell_metrics_start.StaffSystem ~= cell_metrics_end.StaffSystem) then + local next_cell_metrics = finale.FCCellMetrics() + local next_metrics_loaded = next_cell_metrics:LoadAtCell(finale.FCCell(note.Entry.Measure + 1, note.Entry.Staff)) + if not next_metrics_loaded or cell_metrics_start.StaffSystem ~= cell_metrics_end.StaffSystem then + -- note: a tie to an empty measure on the same system will get here, but + -- currently we do not correctly calculate its span. To do so we would have to + -- read the measure separations from the prefs. + horz_end = (cell_metrics_start.MusicStartPos + cell_metrics_start.Width) * staff_scaling + incr_end = cell_metrics_start.RightBarlineWidth + else + horz_end = next_cell_metrics.MusicStartPos * staff_scaling + end + horz_end = horz_end / horz_stretch + else + local entry_metrics = tie_mod:IsStartTie() and entry_metrics_end or entry_metrics_start + local note_index = start_note.NoteIndex + if end_note then + -- if tie_mod:IsStartTie() is true, then end_note will never be nil here (see top if statement), + -- but the Lua delinter can't figure it out, so use an explicit if statement for end_note. + note_index = tie_mod:IsStartTie() and end_note.NoteIndex or note_index + end + horz_end = entry_metrics:GetNoteLeftPosition(note_index) / horz_stretch + if rplacement == finale.TIEPLACE_OVERINNER or rplacement == finale.TIEPLACE_UNDERINNER or rplacement == finale.TIEPLACE_UNDEROUTERSTEM then + incr_end = -INNER_INCREMENT + else + horz_end = horz_end + (entry_metrics_start:GetNoteWidth(note.NoteIndex) * (1.0 - OUTER_NOTE_OFFSET_PCTG)) + end + end + + local start_offset = tie_mod.StartHorizontalPos + if not tie_mod:IsStartPointActive() then + start_offset = calc_prefs_offset_for_endpoint(note, tie_prefs, tie_placement_prefs, lplacement, false, not tie_mod:IsStartTie(), for_pageview) + end + local end_offset = tie_mod.EndHorizontalPos + if not tie_mod:IsEndPointActive() then + end_offset = calc_prefs_offset_for_endpoint(note, tie_prefs, tie_placement_prefs, lplacement, true, not tie_mod:IsStartTie(), for_pageview) + end + + local tie_length = horz_end - horz_start + -- 'undo' page/sys/line % scaling to get absolute EVPUs. + tie_length = tie_length / staff_scaling + tie_length = tie_length + ((end_offset + incr_end) - (start_offset + incr_start)) + return math.floor(tie_length + 0.5) +end + +--[[ +% calc_contour_index + +Calculates the current contour index of a tie based on context and FCTiePrefs. + +@ note (FCNote) the note for which to return the tie direction. +@ tie_mod (FCTieMod) the tie mods for the note, if any. +@ for_pageview (bool) true if calculating for Page View, false for Scroll/Studio View +@ direction (number) one of the TIEMOD_DIRECTION values or nil (if you don't know it yet) +@ [tie_prefs] (FCTiePrefs) use these tie prefs if supplied +: (number) CONTOUR_INDEXES value for tie +: (number) calculated length of tie in EVPU +]] +function tie.calc_contour_index(note, tie_mod, for_pageview, direction, tie_prefs) + if not tie_prefs then + tie_prefs = finale.FCTiePrefs() + tie_prefs:Load(0) + end + direction = direction and direction ~= finale.TIEMODDIR_AUTOMATIC and direction or tie.calc_direction(note, tie_mod, tie_prefs) + local tie_placement_prefs = tie_prefs:CreateTiePlacementPrefs() + if tie_prefs.UseTieEndStyle then + return finale.TCONTOURIDX_TIEENDS + end + local tie_length = calc_tie_length(note, tie_mod, for_pageview, direction, tie_prefs, tie_placement_prefs) + local tie_contour_prefs = tie_prefs:CreateTieContourPrefs() + if tie_length >= tie_contour_prefs:GetSpan(finale.TCONTOURIDX_LONG) then + return finale.TCONTOURIDX_LONG + elseif tie_length <= tie_contour_prefs:GetSpan(finale.TCONTOURIDX_SHORT) then + return finale.TCONTOURIDX_SHORT + end + return finale.TCONTOURIDX_MEDIUM, tie_length +end + +local calc_inset_and_height = function(tie_prefs, tie_contour_prefs, length, contour_index, get_fixed_func, get_relative_func, get_height_func) + -- This function is based on observed Finale behavior and may not precisely capture the exact same interpolated values. + -- However, it appears that Finale interpolates from Short to Medium for lengths in that span and from Medium to Long + -- for lengths in that span. + local height = get_height_func(tie_contour_prefs, contour_index) + local inset = tie_prefs.FixedInsetStyle and get_fixed_func(tie_contour_prefs, contour_index) or get_relative_func(tie_contour_prefs, contour_index) + if tie_prefs.UseInterpolation and contour_index == finale.TCONTOURIDX_MEDIUM then + local interpolation_length, interpolation_percent, interpolation_height_diff, interpolation_inset_diff + if length < tie_contour_prefs:GetSpan(finale.TCONTOURIDX_MEDIUM) then + interpolation_length = tie_contour_prefs:GetSpan(finale.TCONTOURIDX_MEDIUM) - tie_contour_prefs:GetSpan(finale.TCONTOURIDX_SHORT) + interpolation_percent = (interpolation_length - tie_contour_prefs:GetSpan(finale.TCONTOURIDX_MEDIUM) + length) / interpolation_length + interpolation_height_diff = get_height_func(tie_contour_prefs, finale.TCONTOURIDX_MEDIUM) - get_height_func(tie_contour_prefs, finale.TCONTOURIDX_SHORT) + interpolation_inset_diff = get_relative_func(tie_contour_prefs, finale.TCONTOURIDX_MEDIUM) - get_relative_func(tie_contour_prefs, finale.TCONTOURIDX_SHORT) + height = get_height_func(tie_contour_prefs, finale.TCONTOURIDX_SHORT) + if not tie_prefs.FixedInsetStyle then + inset = get_relative_func(tie_contour_prefs, finale.TCONTOURIDX_SHORT) + end + else + interpolation_length = tie_contour_prefs:GetSpan(finale.TCONTOURIDX_LONG) - tie_contour_prefs:GetSpan(finale.TCONTOURIDX_MEDIUM) + interpolation_percent = (interpolation_length - tie_contour_prefs:GetSpan(finale.TCONTOURIDX_LONG) + length) / interpolation_length + interpolation_height_diff = get_height_func(tie_contour_prefs, finale.TCONTOURIDX_LONG) - get_height_func(tie_contour_prefs, finale.TCONTOURIDX_MEDIUM) + interpolation_inset_diff = get_relative_func(tie_contour_prefs, finale.TCONTOURIDX_LONG) - get_relative_func(tie_contour_prefs, finale.TCONTOURIDX_MEDIUM) + end + height = math.floor(0.5 + height + interpolation_height_diff * interpolation_percent) + if not tie_prefs.FixedInsetStyle then + inset = math.floor(0.5 + inset + interpolation_inset_diff * interpolation_percent) + end + end + return inset, height +end + +--[[ +% activate_contour + +Activates the contour fields of the input tie_mod and initializes them with their +default values. If the contour fields are already activated, nothing is changed. Note +that for interpolated Medium span types, the interpolated values may not be identical +to those calculated by Finale, but they should be close enough to make no appreciable +visible difference. + +@ note (FCNote) the note for which to return the tie direction. +@ tie_mod (FCTieMod) the tie mods for the note, if any. +@ for_pageview (bool) true if calculating for Page View, false for Scroll/Studio View +@ [tie_prefs] (FCTiePrefs) use these tie prefs if supplied +: (boolean) returns true if anything changed +]] +function tie.activate_contour(note, tie_mod, for_pageview, tie_prefs) + if tie_mod:IsContourActive() then + return false + end + if not tie_prefs then + tie_prefs = finale.FCTiePrefs() + tie_prefs:Load(0) + end + local direction = tie.calc_direction(note, tie_mod, tie_prefs) + local tie_contour_index, length = tie.calc_contour_index(note, tie_mod, for_pageview, direction, tie_prefs) + local tie_contour_prefs = tie_prefs:CreateTieContourPrefs() + local left_inset, left_height = calc_inset_and_height( + tie_prefs, tie_contour_prefs, length, tie_contour_index, tie_contour_prefs.GetLeftFixedInset, tie_contour_prefs.GetLeftRawRelativeInset, + tie_contour_prefs.GetLeftHeight) + local right_inset, right_height = calc_inset_and_height( + tie_prefs, tie_contour_prefs, length, tie_contour_index, tie_contour_prefs.GetRightFixedInset, tie_contour_prefs.GetRightRawRelativeInset, + tie_contour_prefs.GetRightHeight) + tie_mod:ActivateContour(left_inset, left_height, right_inset, right_height, tie_prefs.FixedInsetStyle) + return true +end + +return tie diff --git a/src/lyrics_baseline_move_down.lua b/src/lyrics_baseline_move_down.lua deleted file mode 100644 index c610bd44..00000000 --- a/src/lyrics_baseline_move_down.lua +++ /dev/null @@ -1,76 +0,0 @@ -function plugindef() - finaleplugin.RequireSelection = true - finaleplugin.Author = "Robert Patterson" - finaleplugin.Version = "1.0" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Date = "June 23, 2021" - finaleplugin.CategoryTags = "Lyric" - finaleplugin.AuthorURL = "http://robertgpatterson.com" - return "Move Lyric Baselines Down", "Move Lyrics Baselines Down", - "Moves all selected lyrics baselines down one space" -end - -local configuration = require("library.configuration") - -local config = { - nudge_down_evpus = 24 -} - -if nil ~= configuration then - configuration.get_parameters("lyrics_baseline_move.config.txt", config) -end - -local baseline_types = { - [finale.BASELINEMODE_LYRICSVERSE] = function() return finale.FCVerseLyricsText() end, - [finale.BASELINEMODE_LYRICSCHORUS] = function() return finale.FCChorusLyricsText() end, - [finale.BASELINEMODE_LYRICSSECTION] = function() return finale.FCSectionLyricsText() end -} - -function find_valid_lyric_nums() - local valid_lyric_nums = {} - for baseline_type, lyrics_text_class_constructor in pairs(baseline_types) do - local lyrics_text_class = lyrics_text_class_constructor() - for i = 1, 32767, 1 do - if lyrics_text_class:Load(i) then - local str = finale.FCString() - lyrics_text_class:GetText(str) - if not str:IsEmpty() then - valid_lyric_nums[{baseline_type, i}] = 1 - end - end - end - end - return valid_lyric_nums -end - -function lyrics_baseline_move_down() - local region = finenv.Region() - local systems = finale.FCStaffSystems() - systems:LoadAll() - - local start_measure = region:GetStartMeasure() - local end_measure = region:GetEndMeasure() - local system = systems:FindMeasureNumber(start_measure) - local lastSys = systems:FindMeasureNumber(end_measure) - local system_number = system:GetItemNo() - local lastSys_number = lastSys:GetItemNo() - local start_slot = region:GetStartSlot() - local end_slot = region:GetEndSlot() - - local valid_lyric_nums = find_valid_lyric_nums() - - for i = system_number, lastSys_number, 1 do - local baselines = finale.FCBaselines() - for lyric_info, _ in pairs(valid_lyric_nums) do - local baseline_type, lyric_number = table.unpack(lyric_info) - baselines:LoadAllForSystem(baseline_type, i) - for j = start_slot, end_slot do - bl = baselines:AssureSavedLyricNumber(baseline_type, i, region:CalcStaffNumber(j), lyric_number) - bl.VerticalOffset = bl.VerticalOffset - config.nudge_down_evpus - bl:Save() - end - end - end -end - -lyrics_baseline_move_down() diff --git a/src/lyrics_baseline_move_up.lua b/src/lyrics_baseline_move_up.lua deleted file mode 100644 index 629ecebc..00000000 --- a/src/lyrics_baseline_move_up.lua +++ /dev/null @@ -1,76 +0,0 @@ -function plugindef() - finaleplugin.RequireSelection = true - finaleplugin.Author = "Robert Patterson" - finaleplugin.Version = "1.0" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Date = "June 23, 2021" - finaleplugin.CategoryTags = "Lyric" - finaleplugin.AuthorURL = "http://robertgpatterson.com" - return "Move Lyric Baselines Up", "Move Lyrics Baselines Up", - "Moves all selected lyrics baselines up one space" -end - -local configuration = require("library.configuration") - -local config = { - nudge_up_evpus = 24 -} - -if nil ~= configuration then - configuration.get_parameters("lyrics_baseline_move.config.txt", config) -end - -local baseline_types = { - [finale.BASELINEMODE_LYRICSVERSE] = function() return finale.FCVerseLyricsText() end, - [finale.BASELINEMODE_LYRICSCHORUS] = function() return finale.FCChorusLyricsText() end, - [finale.BASELINEMODE_LYRICSSECTION] = function() return finale.FCSectionLyricsText() end -} - -function find_valid_lyric_nums() - local valid_lyric_nums = {} - for baseline_type, lyrics_text_class_constructor in pairs(baseline_types) do - local lyrics_text_class = lyrics_text_class_constructor() - for i = 1, 32767, 1 do - if lyrics_text_class:Load(i) then - local str = finale.FCString() - lyrics_text_class:GetText(str) - if not str:IsEmpty() then - valid_lyric_nums[{baseline_type, i}] = 1 - end - end - end - end - return valid_lyric_nums -end - -function lyrics_baseline_move_up() - local region = finenv.Region() - local systems = finale.FCStaffSystems() - systems:LoadAll() - - local start_measure = region:GetStartMeasure() - local end_measure = region:GetEndMeasure() - local system = systems:FindMeasureNumber(start_measure) - local lastSys = systems:FindMeasureNumber(end_measure) - local system_number = system:GetItemNo() - local lastSys_number = lastSys:GetItemNo() - local start_slot = region:GetStartSlot() - local end_slot = region:GetEndSlot() - - local valid_lyric_nums = find_valid_lyric_nums() - - for i = system_number, lastSys_number, 1 do - local baselines = finale.FCBaselines() - for lyric_info, _ in pairs(valid_lyric_nums) do - local baseline_type, lyric_number = table.unpack(lyric_info) - baselines:LoadAllForSystem(baseline_type, i) - for j = start_slot, end_slot do - bl = baselines:AssureSavedLyricNumber(baseline_type, i, region:CalcStaffNumber(j), lyric_number) - bl.VerticalOffset = bl.VerticalOffset + config.nudge_up_evpus - bl:Save() - end - end - end -end - -lyrics_baseline_move_up() diff --git a/src/lyrics_baseline_reset.lua b/src/lyrics_baseline_reset.lua deleted file mode 100644 index 64e6dffc..00000000 --- a/src/lyrics_baseline_reset.lua +++ /dev/null @@ -1,47 +0,0 @@ -function plugindef() - finaleplugin.RequireSelection = true - finaleplugin.Author = "Robert Patterson" - finaleplugin.Version = "1.0" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Date = "July 7, 2021" - finaleplugin.CategoryTags = "Lyric" - finaleplugin.AuthorURL = "http://robertgpatterson.com" - return "Reset Lyric Baselines", "Reset Lyrics Baselines", - "Resets all selected lyrics baselines to default" -end - -local baseline_types = { - finale.BASELINEMODE_LYRICSVERSE, - finale.BASELINEMODE_LYRICSCHORUS, - finale.BASELINEMODE_LYRICSSECTION -} - -function lyrics_baseline_reset() - local region = finenv.Region() - local systems = finale.FCStaffSystems() - systems:LoadAll() - - local start_measure = region:GetStartMeasure() - local end_measure = region:GetEndMeasure() - local system = systems:FindMeasureNumber(start_measure) - local lastSys = systems:FindMeasureNumber(end_measure) - local system_number = system:GetItemNo() - local lastSys_number = lastSys:GetItemNo() - local start_slot = region:GetStartSlot() - local end_slot = region:GetEndSlot() - - for i = system_number, lastSys_number, 1 do - local baselines = finale.FCBaselines() - for _, baseline_type in pairs(baseline_types) do - baselines:LoadAllForSystem(baseline_type, i) - for baseline in each(baselines) do - local baseline_slot = region:CalcSlotNumber(baseline.Staff) - if (start_slot <= baseline_slot) and (baseline_slot <= end_slot) then - baseline:DeleteData() - end - end - end - end -end - -lyrics_baseline_reset() diff --git a/src/pitch_entry_double_at_interval.lua b/src/pitch_entry_double_at_interval.lua new file mode 100644 index 00000000..9b20e9a9 --- /dev/null +++ b/src/pitch_entry_double_at_interval.lua @@ -0,0 +1,66 @@ +function plugindef() + finaleplugin.RequireSelection = true + finaleplugin.Author = "Nick Mazuk" + finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" + finaleplugin.Version = "2.0" + finaleplugin.Date = "May 5, 2022" + finaleplugin.CategoryTags = "Pitch" + finaleplugin.AuthorURL = "https://nickmazuk.com" + finaleplugin.MinJWLuaVersion = 0.62 + finaleplugin.AdditionalMenuOptions = [[ + Octave Doubling Down + Double third up + Double third down + ]] + finaleplugin.AdditionalDescriptions = [[ + Doubles the current note an octave lower + Doubles the current note a diatonic third higher + Doubles the current note a diatonic third lower + ]] + finaleplugin.AdditionalPrefixes = [[ + input_interval = -7 + input_interval = 2 + input_interval = -2 + ]] + finaleplugin.Notes = [[ + This script doubles selected entries at a specified diatonic interval above or below. + By default, it creates menu options to double an octave up and down as well as options + to double a third up and down. RGP Lua allows you to add further menu options by creating + additional instances of the script file and setting Optional Menu Text and Optional Prefix. + To avoid confusion, you should also set the Optional Description. If you omit Optional Undo Text, + the undo text will be the same as the menu option. + + Here is an example that creates a "Double Fifth Up" menu option. + + - Optional Menu Text: `Double Fifth Up` + - Optional Description: `Doubles the current note a diatonic fifth higher` + - Optional Prefix: `input_interval = 4` + + Intervals are defined as 0=unison, 1=second, 2=third, etc. Positive values transpose up and + negative values transpose down. See the "AdditionalPrefixes" above for examples. + ]] + return "Octave Doubling Up", "Octave Doubling Up", "Doubles the current note an octave higher" +end + +local transposition = require("library.transposition") +local note_entry = require("library.note_entry") + +function pitch_entry_double_at_interval(interval) + for entry in eachentrysaved(finenv.Region()) do + local note_count = entry.Count + local note_index = 0 + for note in each(entry) do + note_index = note_index + 1 + if note_index > note_count then + break + end + local new_note = note_entry.duplicate_note(note) + if new_note then + transposition.diatonic_transpose(new_note, interval) + end + end + end +end + +input_interval = input_interval or 7 +pitch_entry_double_at_interval(input_interval) diff --git a/src/pitch_entry_double_octave_down.lua b/src/pitch_entry_double_octave_down.lua deleted file mode 100644 index f6023dce..00000000 --- a/src/pitch_entry_double_octave_down.lua +++ /dev/null @@ -1,32 +0,0 @@ -function plugindef() - finaleplugin.RequireSelection = true - finaleplugin.Author = "Nick Mazuk" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Version = "1.0.2" - finaleplugin.Date = "March 31, 2021" - finaleplugin.CategoryTags = "Pitch" - finaleplugin.AuthorURL = "https://nickmazuk.com" - return "Octave Doubling Down", "Octave Doubling Down", "Doubles the current note an octave lower" -end - -local transposition = require("library.transposition") -local note_entry = require("library.note_entry") - -function pitch_entry_double_octave_down() - for entry in eachentrysaved(finenv.Region()) do - local note_count = entry.Count - local note_index = 0 - for note in each(entry) do - note_index = note_index + 1 - if note_index > note_count then - break - end - local new_note = note_entry.duplicate_note(note) - if nil ~= new_note then - transposition.change_octave(new_note, -1) - end - end - end -end - -pitch_entry_double_octave_down() diff --git a/src/pitch_entry_double_octave_up.lua b/src/pitch_entry_double_octave_up.lua deleted file mode 100644 index c3f5800c..00000000 --- a/src/pitch_entry_double_octave_up.lua +++ /dev/null @@ -1,32 +0,0 @@ -function plugindef() - finaleplugin.RequireSelection = true - finaleplugin.Author = "Nick Mazuk" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Version = "1.0.2" - finaleplugin.Date = "March 31, 2021" - finaleplugin.CategoryTags = "Pitch" - finaleplugin.AuthorURL = "https://nickmazuk.com" - return "Octave Doubling Up", "Octave Doubling Up", "Doubles the current note an octave higher" -end - -local transposition = require("library.transposition") -local note_entry = require("library.note_entry") - -function pitch_entry_double_octave_up() - for entry in eachentrysaved(finenv.Region()) do - local note_count = entry.Count - local note_index = 0 - for note in each(entry) do - note_index = note_index + 1 - if note_index > note_count then - break - end - local new_note = note_entry.duplicate_note(note) - if nil ~= new_note then - transposition.change_octave(new_note, 1) - end - end - end -end - -pitch_entry_double_octave_up() diff --git a/src/pitch_entry_double_third_down.lua b/src/pitch_entry_double_third_down.lua deleted file mode 100644 index 04f195ef..00000000 --- a/src/pitch_entry_double_third_down.lua +++ /dev/null @@ -1,32 +0,0 @@ -function plugindef() - finaleplugin.RequireSelection = true - finaleplugin.Author = "Nick Mazuk" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Version = "1.0.2" - finaleplugin.Date = "March 31, 2021" - finaleplugin.CategoryTags = "Pitch" - finaleplugin.AuthorURL = "https://nickmazuk.com" - return "Double third down", "Double third down", "Doubles the current note a diatonic third lower" -end - -local transposition = require("library.transposition") -local note_entry = require("library.note_entry") - -function pitch_entry_double_third_down() - for entry in eachentrysaved(finenv.Region()) do - local note_count = entry.Count - local note_index = 0 - for note in each(entry) do - note_index = note_index + 1 - if note_index > note_count then - break - end - local new_note = note_entry.duplicate_note(note) - if nil ~= new_note then - transposition.diatonic_transpose(new_note, -2) - end - end - end -end - -pitch_entry_double_third_down() diff --git a/src/pitch_entry_double_third_up.lua b/src/pitch_entry_double_third_up.lua deleted file mode 100644 index cfdf5c19..00000000 --- a/src/pitch_entry_double_third_up.lua +++ /dev/null @@ -1,32 +0,0 @@ -function plugindef() - finaleplugin.RequireSelection = true - finaleplugin.Author = "Nick Mazuk" - finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Version = "1.0.2" - finaleplugin.Date = "March 31, 2021" - finaleplugin.CategoryTags = "Pitch" - finaleplugin.AuthorURL = "https://nickmazuk.com" - return "Double third up", "Double third up", "Doubles the current note a diatonic third higher" -end - -local transposition = require("library.transposition") -local note_entry = require("library.note_entry") - -function pitch_entry_double_third_up() - for entry in eachentrysaved(finenv.Region()) do - local note_count = entry.Count - local note_index = 0 - for note in each(entry) do - note_index = note_index + 1 - if note_index > note_count then - break - end - local new_note = note_entry.duplicate_note(note) - if nil ~= new_note then - transposition.diatonic_transpose(new_note, 2) - end - end - end -end - -pitch_entry_double_third_up()