2

I'm trying to do something with MuseScore that I'm not sure is possible, and I'd appreciate some help trying to figure out a creative way to do this.

So I'm trying to write in an octatonic mode with eight distinct pitches, whose frequency values I provide. The eight pitches are unrelated to pitches in 12-TET. The pitches are defined by ratios to a fundamental/tonic, but I imagine that it would be a lot easier for me to input absolute frequencies than ratios.

Here is functionality I am looking for: (I'm aware that not all of this might be possible)

  • Some way to define the pitches that I'm looking for
  • The octave (nonave?) is nine units wide on the staff, not eight
  • Can I bind "H" to the note above G and below A in this scale?
  • Some way to play back the music I write in this mode
  • I don't particularly care about accidentals; I don't intend to use them.
  • Cool but not strictly necessary: a custom clef

Some options I have considered:

  • I saw there is a "Tuning" plugin, but it doesn't seem powerful enough to do what I'm trying to do (or maybe it is, and I just haven't figured it out yet?)
  • Maybe I could define a new instrument somehow that can do what I want, similarly to how the drum set staff doesn't mean the same thing as the standard 5-line staff?

I tried asking this question on MuseScore's support forum, but got no response. I'm using MuseScore 4.0.2, but I could switch back to 3.x if there are plugins written for an earlier version that have this functionality. If this is impossible, but it can be done using a different free software, feel free to include that, but I'm primarily looking for answers that use MuseScore.

Here's an example screenshot of what this could look like (created using MuseScore, but none of the features relating to pitch are implemented) Octatonic scale going up and down

1 Answer 1

3

I’m quite unsure about what you’re actual question is, as clearly you’ve already found a way to notate such, and clearly you may extend the note names however you like.

A custom clef will not be really possible in MuseScore unless you create you own Notation font file that substitutes an existing clef with what you want.

Regarding playback: Simply create a small MuseScore plugin like this:

import MuseScore 3.0
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.3
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.1

MuseScore {
    version: "3.0.5"
    title: "To Nonic"
    pluginType: "dialog"
    categoryCode: "playback"

    property var offsetTextWidth: 200;
    property var offsetLabelAlignment: 0x02 | 0x80;
    
    property var offset: [ 0, 150, 300, 450, 600, 750, 900, 1050 ];

    onRun: {
        if (!curScore) {
            error("No score open.\nThis plugin requires an open score to run.\n")
            quit()
        }
    }

    function applyTemperament()
    {
        var selection = new scoreSelection()
        curScore.startCmd()
        selection.map(filterNotes, reTune(getFinalTuning()))
        curScore.endCmd()
        return true
    }

    function filterNotes(element)
    {
        return element.type == Element.CHORD
    }

    function reTune(tuning) {
        return function(chord, cursor) {
            for (var i = 0; i < chord.notes.length; i++) {
                var note = chord.notes[i]
                note.tuning = tuning(note)
            }
        }
    }

    function scoreSelection() {
        const SCORE_START = 0
        const SELECTION_START = 1
        const SELECTION_END = 2
        var fullScore
        var startStaff
        var endStaff
        var endTick
        var inRange
        var rewind
        var cursor = curScore.newCursor()
        cursor.rewind(SELECTION_START)
        if (cursor.segment) {
            startStaff = cursor.staffIdx
            cursor.rewind(SELECTION_END)
            endStaff = cursor.staffIdx;
            endTick = 0 // unused
            if (cursor.tick === 0) {
               endTick = curScore.lastSegment.tick + 1;
            } else {
               endTick = cursor.tick;
            }
            inRange = function() {
                return cursor.segment && cursor.tick < endTick
            }
            rewind = function (voice, staff) {
                // no idea why, but if there is a selection then
                // we need to rewind the cursor *before* setting
                // the voice and staff index.
                cursor.rewind(SELECTION_START)
                cursor.voice = voice
                cursor.staffIdx = staff
            }
        } else {
            startStaff = 0
            endStaff  = curScore.nstaves - 1
            inRange = function () {
                return cursor.segment
            }
            rewind = function (voice, staff) {
                // no idea why, but if there's no selection then
                // we need to rewind the cursor *after* setting
                // the voice and staff index.
                cursor.voice = voice
                cursor.staffIdx = staff
                cursor.rewind(SCORE_START)
            }
        }

        this.map = function(filter, process) {
            for (var staff = startStaff; staff <= endStaff; staff++) {
                for (var voice = 0; voice < 4; voice++) {
                    rewind(voice, staff)
                    while (inRange()) {
                        if (cursor.element && filter(cursor.element)) {
                            process(cursor.element, cursor)
                        }
                        cursor.next()
                    }
                }
            }
        }
    }

    function error(errorMessage) {
        errorDialog.text = qsTr(errorMessage)
        errorDialog.open()
    }

    function getFinalTuning() {
        return function(note) {
            
            var tpc = note.tpc; // Pitch class = circle of fifths position with C == 14
            var refpc = (((tpc - 13) % 7) + 7) % 7; // f = 0, c = 1, ..., b = 6
            var alt = (tpc - 13 - refpc) / 7; // 0 = nat, -1 = flat, 1 = sharp, ...
            var refpc2 = ((4 * refpc) + 3) % 7; // c = 0, d = 1, ...
            var pitch = note.pitch; // midi pitch, C4 = 60
            var refpitch = pitch - alt; // e.g. Dbb4 = 60, refpitch = D = 62
            var octave = (refpitch - (refpitch % 12) - 60) / 12; // Octave from C4 (note: Cbbbbbbbbbbbb5 is still an octave up!)
            
            var nonpc = (((refpc2 - octave) % 8) + 8) % 8; // c4, ..., a4, b4 -> c4, ..., h4, a4, then c5 -> b4, c6 -> a4, ... . Also c3 -> d3, ...
            var deltanonave = (refpc2 - octave - nonpc) / 8;
            var nonave = octave + deltanonave;
            
            var tuning_refpc = 200 * refpc2 + 1200 * octave;
            if (refpc2 > 2) {
                  tuning_refpc = tuning_refpc - 100;
            }
            
            var tuning = getOffset(nonpc) + 1200 * nonave;

            return tuning - tuning_refpc;
        }
    }


    function getOffsetS(id) {
        switch (id) {
              case 0:
                    return final_c.text
              case 1:
                    return final_d.text
              case 2:
                    return final_e.text
              case 3:
                    return final_f.text
              case 4:
                    return final_g.text
              case 5:
                    return final_h.text
              case 6:
                    return final_a.text
              case 7:
                    return final_b.text
        }
    }

    function getOffset(id) {
          return parseFloat(getOffsetS(id))
    }

    

    Rectangle {
        color: "transparent"
        anchors.fill: parent

        ColumnLayout {

            ColumnLayout {
                ColumnLayout {

                    Label {
                        text: "C"
                        Layout.alignment: offsetLabelAlignment
                    }
                    TextField {
                        Layout.maximumWidth: offsetTextWidth
                        id: final_c
                        text: offset[0]
                        readOnly: false
                        validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 }
                    }

                    Label {
                        text: "D"
                        Layout.alignment: offsetLabelAlignment
                    }
                    TextField {
                        Layout.maximumWidth: offsetTextWidth
                        id: final_d
                        text: offset[1]
                        readOnly: false
                        validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 }
                    }

                    Label {
                        text: "E"
                        Layout.alignment: offsetLabelAlignment
                    }
                    TextField {
                        Layout.maximumWidth: offsetTextWidth
                        id: final_e
                        text: offset[2]
                        readOnly: false
                        validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 }
                    }

                    Label {
                        text: "F"
                        Layout.alignment: offsetLabelAlignment
                    }
                    TextField {
                        Layout.maximumWidth: offsetTextWidth
                        id: final_f
                        text: offset[3]
                        readOnly: false
                        validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 }
                    }

                    Label {
                        text: "G"
                        Layout.alignment: offsetLabelAlignment
                    }
                    TextField {
                        Layout.maximumWidth: offsetTextWidth
                        id: final_g
                        text: offset[4]
                        readOnly: false
                        validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 }
                    }

                    Label {
                        text: "H"
                        Layout.alignment: offsetLabelAlignment
                    }
                    TextField {
                        Layout.maximumWidth: offsetTextWidth
                        id: final_h
                        text: offset[5]
                        readOnly: false
                        validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 }
                    }

                    Label {
                        text: "A"
                        Layout.alignment: offsetLabelAlignment
                    }
                    TextField {
                        Layout.maximumWidth: offsetTextWidth
                        id: final_a
                        text: offset[6]
                        readOnly: false
                        validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 }
                    }

                    Label {
                        text: "B"
                        Layout.alignment: offsetLabelAlignment
                    }
                    TextField {
                        Layout.maximumWidth: offsetTextWidth
                        id: final_b
                        text: offset[7]
                        readOnly: false
                        validator: DoubleValidator { bottom: -99.9; decimals: 1; notation: DoubleValidator.StandardNotation; top: 99.9 }
                    }

                }

                RowLayout {
                    Button {
                        id: applyButton
                        text: qsTranslate("PrefsDialogBase", "Apply")
                        onClicked: {
                            if (applyTemperament()) {
                                quit()
                            }
                        }
                    }
                    Button {
                        id: cancelButton
                        text: qsTranslate("PrefsDialogBase", "Cancel")
                        onClicked: {
                            quit()
                        }
                    }
                }
            }
        }
    }

    MessageDialog {
        id: errorDialog
        title: "Error"
        text: ""
        onAccepted: {
            errorDialog.close()
        }
    }
}

This will then allow you to quickly tune each note to the desired value. This plugin takes C4 as base point.

1
  • Worked like a charm, thank you for your help!
    – AAM111
    May 8, 2023 at 21:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.