Musical Notation in SC using musicXML

Would it be possible that you share its code? it can also be only the initial bars. I’ve been looking for examples of contemporary scores using Lilypond and I couldn’t find complete examples, only super small snippets…

There is the whole thing. Let me know if you have any question (probably in a message).
J

2 Likes

Thanks I didn’t know about SCAMP! But music21 by default now support musicxml and they’re going to drop lilypond

I tested @josh’s Ctk Quark by translating the following example from music21:

from music21 import *
print (serial.rowToMatrix([2,1,9,10,5,3,4,0,8,7,6,11]) )

I do not know how serial.rowToMatrix is coded, but my version gives the same result.

(
~matrix_12tone = { |array|
	var matrix = 12.collect { |i| array[i].postln; array - array[i] % 12 };
	matrix.do { |item| item.replace(11, \e).replace(10, \t).postln }
};

~mXML = { |array, file|
	var xmlScore, xmlPart;
	xmlScore = XMLScore();
	xmlPart = XMLPart(meter: XMLMeter(12, 4), key: XMLKey.major(\C));
	xmlScore.add(xmlPart);
	array.do{ |keyNum|
		xmlPart.add(XMLNote(keyNum, xmlPart.now, \q))
	};
	xmlScore.output(file);
};

~vXML = { |file, app|
	Platform.case(
		\linux,   { |app = "reaper"| app + file.quote },
		\osx,     { |app = "Dorico 4"| "open -a" + app.quote + file.quote },
		\windows, { |app = "C:/Program Files/REAPER (x64)/reaper.exe"| "start" + "".quote + app.quote + file.quote }
	).unixCmd;
} 
)

(
~testMusic = ~matrix_12tone.([2,1,9,10,5,3,4,0,8,7,6,11]);
~testFile = "~/Downloads/test.musicXML".standardizePath;
~mXML.(~testMusic.flatten + 60, ~testFile);
~testFile.openOS;
~vXML.(~testFile);
)

Then I tried to process it using Ctk Quark, and it works on all three platforms. I have tested it on Monterey, Windows 11 and Ubuntu. I will try a more complicated notation example later!




1 Like

Oh wow. I’m pleasantly surprised :smiley:
I did not do as much with the MusicXML generator as I did with Guido. But if this may actually be used by others, let’s figure out how to add what we need!

2 Likes

I am glad to have given you a pleasant surprise!

Your Quark seems to be the only one that supports writing musicXML in SC, as FOMUS does not seem to install on modern OSes (at least on my end).

On my first attempt I noticed the following:

  1. sclang warns of an deprecated method as follows:
WARNING: Called from XMLVoice:fillMeasures, method SimpleNumber:quantize is deprecated and will be removed. Use SimpleNumber:snap instead.
The definition of 'SimpleNumber:quantize' is to be found here: '/Applications/SuperCollider.app/Contents/Resources/SCClassLibrary/deprecated/3.10/deprecated-3.10.sc'
  1. XML related help documents cannot be read on the HelpBrowser in SC-IDE.

  2. I think it is not necessary to show time signature and clef in every bar.

I have read your [thread] (The Future of SuperCollider Development Efforts). I am not sure what to do. My English and programming skills are not advanced enough to participate in the development or online meeting. I could get involved in reporting some minor issues, including finding typos and writing help documents, but I am not sure that my involvement would really help others.

As for your ctk quark, I might start a pull request to fix the first two things mentioned above, but I am not sure if I can do the task myself and if you want to revise your XML related classes again.

1 Like

Thanks for the example, and thanks Josh for the Quark. Yes, it works. It can be a good starting point indeed.

The example below this message already gives music XML error messages in Dorico and Musescore, but the file still opens. Maybe the first step would be to double-check the new musicxml standard (4.0).

I would suggest using Rational numbers (see Quark) instead of floats, maybe it would work better in general. I don’t know if I’m alone here, but makes sense to me.

Also, a class to deal with tuplets would be better than just passing a number to each duration since MuscXml is able to tag different tuplets (start and end), and I tend to think this would work for more real-case uses of complex rhythms: reference. Is that the way Guido deals with tuplets? I remember that’s the only way I found out how to do it.

BTW, Guido could be also interesting because there are tools to display the music interactively in real-time. (https://guido.grame.fr/)

(
~matrix_12tone = { |array|
	var matrix = 12.collect { |i| array[i].postln; array - array[i] % 12 };
	matrix.do { |item| item.replace(11, \e).replace(10, \t).postln }
};

~mXML = { |array, file|
	var xmlScore, xmlPart;
	xmlScore = XMLScore();
	xmlPart = XMLPart(meter: XMLMeter(4, 4), key: XMLKey.major(\C));
	xmlScore.add(xmlPart);
	array.do{ |keyNum|
        xmlPart.add(XMLNote(keyNum, xmlPart.now, \e, (3/2)))
	};
	xmlScore.output(file);
};

~vXML = { |file, app|
	Platform.case(
		\linux,   { |app = "reaper"| app + file.quote },
		\osx,     { |app = "Dorico 4"| "open -a" + app.quote + file.quote },
		\windows, { |app = "C:/Program Files/REAPER (x64)/reaper.exe"| "start" + "".quote + app.quote + file.quote }
	).unixCmd;
} 
)

(
~testMusic = ~matrix_12tone.([2,1,9,10,5,3,4,0,8,7,6,11]);
~testFile = "~/Downloads/test.musicXML".standardizePath;
~mXML.(~testMusic.flatten + 60, ~testFile);
~testFile.openOS;
~vXML.(~testFile);
)

Musescore:

Dorico (no accidentals, why?)

1 Like

Thanks for the examples.

I often see error messages when opening musicXML, so I am not too worried about the error messages. However, not displaying accidentals is a problem.

I have continued to test your code and have noticed that some applications display accidentals and others do not. I think this is due to the musicXML version of the applications.

I have attached more examples with error messages.

  1. The following applications do not show the accidentals as you pointed out.
  • Finale 27.3.0.160
    • score:

    • error messages:

      XML error in file /Users/prko/Downloads/test.musicXML at line 38:
      cvc-complex-type.2.4.a: Invalid content was found starting with element 'time-modification'. One of '{beam, notations, lyric, play, listen}' is expected.
      
      XML error in file /Users/prko/Downloads/test.musicXML at line 53:
      cvc-complex-type.2.4.a: Invalid content was found starting with element 'time-modification'. One of '{beam, notations, lyric, play, listen}' is expected.
      
      XML error in file /Users/prko/Downloads/test.musicXML at line 68:
      cvc-complex-type.2.4.a: Invalid content was found starting with element 'time-modification'. One of '{beam, notations, lyric, play, listen}' is expected.
      
      XML error in file /Users/prko/Downloads/test.musicXML at line 83:
      cvc-complex-type.2.4.a: Invalid content was found starting with element 'time-modification'. One of '{beam, notations, lyric, play, listen}' is expected.
      
      XML error in file /Users/prko/Downloads/test.musicXML at line 98:
      cvc-complex-type.2.4.a: Invalid content was found starting with element 'time-modification'. One of '{beam, notations, lyric, play, listen}' is expected.
      
      Further XML validation errors will be ignored.
      
  1. The following applications also do not show the accidentals as you pointed out, and they also show the key as B-flat major.
  • Dorico SE 5.0.10.2029 and Dorico Pro 4.3.30.1132

    • score:

    • error messages:

      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 38 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 53 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 68 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 83 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 98 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 113 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 128 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 143 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 158 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 173 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 188 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 203 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 404 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 419 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 434 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 449 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 464 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 479 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 494 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 509 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 524 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 539 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 554 of /Users/prko/Desktop/test.xml
      ERROR - Element 'time-modification': This element is not expected. Expected is one of ( notations, lyric, play, listen ).
      At line 569 of /Users/prko/Desktop/test.xml
      
  • Logic Pro 10.7.8

  • MuseScore 3.6.2.548020600 and MuseScore 4.0.2-230651546.

    • score:

    • error messages:

      File '/Users/prko/Desktop/test.xml' is not a valid MusicXML file.
      Do you want to try to load this file anyway?
      
      Fatal error: line 38, column 35: Element time-modification is not defined in this scope.
      
  1. The following application shows the accidentals, but triplets are not displayed correctly.

The tuplet argument in XMLNote seems to cause the problem (maybe because MusicXML has changed). We would need to double-check a bit deeper into the standard, and what should be generated. A separate class for tuplet (as a container for notes and rests) seems to make sense, that’s my first guess.

1 Like

I wrote a function that creates a musicXML file when the musical information is written as in example 1. With this, sclang can also easily request a server to play music. I attach this function with 5 examples. Example 3, 4 and 5 also contain how to request a server to play music. The examples below work on my end, but the function should be rewritten whenever other features are added. I would like to know what other users think about such work in SC.

1. Function: ~exportXML
https://www.dropbox.com/scl/fi/mxgk0ej96kqe0jt7i8au0/musicXMLgenerator.scd?rlkey=7sszsg4a776k1iu27k0pcq5ig&dl=0

2. Example 1: how to notate

(
/* A score should be written in bar notation as an array. */
~score1 = [

    /* The first element should be information about the composer, title and copyright. */
    // - Access: ~score1[0]
    (   title: 'untitled', composer: 'me', rights: '©'),

    /* The elements from the second to the last are musical notation events,
    and each event contains the musical information for each bar. */
    // - Access to all bars: ~score1[1..]
    // - Access to a specific bar: ~score1[barNumber]
    //                         ex.) ~score1[1]

    (   /* The notation information of a bar should be constructed as elements within an event. */
        // It contains the "bar number" with the key \bar
        // and all the parts of the bar with keys like \p1, \p2, etc:

        bar: 1,
        // - Access: ~score1[barNumber][\bar]
        //           ex.) ~score1[1][\bar]

        // ATTENTION:
        // The first bar should contain all the parts of the whole score even though
        // one or more particular parts do not appear in the first bar.

        /* Each part should be an event that contains its notation information in the bar. */
        // The value of the event key is an array containing the notation information of its part in the bar:
        p1:
        (
            lbl: \Soprano,
            // The \lbl key should contain its part name or staff label.
            // - Access: ~score1[barNumber][partNumber][\lbl]
            //           ex.) ~score1[1][\p1][\lbl]

            atr: // The \atr key should contain its attributes as an event:
            (
                key: // This defines the key signature.
                [ // ATTENTION: the order of the elements is important!
                    0 // The number of sharps or flats:
                    //    2:   two sharps, used for D major or B Minor
                    //    1:   one sharp, used for G major or E minor
                    //    0:   no sharps and no flats
                    //   -1:   one flat
                    //   -2:   two flats;
                    ,
                    \none // mode:
                    //         \none: atonal
                    //         \C: C-major
                    //         \a: A-minor
                ]
                ,

                time: // This defines the time signature.
                \x //       \x: senza-misura
                /*
                [ // ATTENTION: the order of the elements is important!
                4 // upper numeral (top number)
                ,
                4 // lower numeral (bottom number)
                ]
                */
                ,

                staves: // This defines the number of staves.
                1       // The number of staves
                ,

                clef: // This defines the clef and its position on each staff.
                [ // ATTENTION: the order of the elements is important!

                    // The clef definition for the first staff:
                    [ // ATTENTION: the order of the elements is important!
                        \c // The clef symbol:
                        //    \G: g-clef
                        //    \C: c-clef
                        //    \F: f clef
                        ,
                        1  // Postition on the staff:
                        //    1: the bottom line
                        //    5: the fifth line from the bottom
                    ]
                ]
                /*
                ,
                trans: // This defines the transposition properties of the part, if necessary.
                [
                -1 // diatonic transposition
                ,
                -2 // chromatic transposition
                ,
                0 // octave transposition
                ]
                */
            ),

            v1: // voice 1. Its elements should be constructed as an array.
            [
                // ATTENTION:
                // 1. The order of the elements is important!
                // 2. Each element of a voice should be an array!

                [\t, 120]
                // The array element with \t as the first element and a number as the second element
                // defines the tempo by the number of quarter notes per minute.
                ,

                [\f],
                // The one-element array with the following symbols defines dynamic marks:
                // - \ffff, \fff, \ff, \f,  \mf, \mp, \p,  \pp, \ppp, \pppp, \sf
                // - \s4,   \s3,  \s2, \s1, \s0, \q0, \q1, \q2, \q3,  \q4

                [\c4]
                // The array containing a scientific pitch name defines the pitch of the note.
                // - S: double sharp
                // - s: sharp
                // - (n: natural) can be ommited
                // - f: flat
                // - F: double flat
                // A quarter tone could be expressed as follows:
                // - \c5:  c5 natural
                // - \cq5: a quarter-tone raised c5
                // - \cs5: c#5
                // - \cQ5: a quarter-tone raised c#5
                // Its duration follows the last duration defined with the previous note, if any.
                // If the note is the first note of the entire notation information,
                // it follows the default value (crotchet).
                // Its dynamic mark follows the last dynamic mark defined with the previous note, if it exists.
                // If the note is the first note of the whole notation information,
                // it will follow the default value (mp).
                ,

                [\cn4],

                [62]
                // The array containing a number defines the pitch of the note.
                // The number should be an integer or a float with a single decimal ending in 0.5 as follows:
                // - 72   = \c5:  c5 natural
                // - 72.5 = \cq5: one quarter-tone raised c5
                // - 73   = \cs5: c#5
                // - 73.5 = \cQ5: one quarter-tone raised c#5
                // A float with a single decimal ending in 0.5 indicates a quarter tone.
                // The range of numbers supported is as follows:
                // - range: 0 ~ 127
                // Its duration and dynamic mark are the same as described above.
                ,

                [[64, 64.5, \g4, \gq4]]
                // A chord (notes of equal duration) should be defined as an array.
                // Its duration and dynamic mark are the same as described above.
                ,

                [67],

                [65, \w]
                // The two-element array containing a scientific pitch name followed by a duration defines
                // the pitch of the note and its duration. The following durations are ready to use:
                //
                // Durations                                  Symbols
                //
                // - double-dotted maxima:                   \Dm  \D_
                // - dotted maxima:                          \dm  \d_
                // - maxima:                                  \m   \_
                // - double-dotted longa:                    \Dl  \D0
                // - dotted longa:                           \dl  \d0
                // - longa:                                   \l   \0  0
                // - double-dotted breve:                    \Db  \D9
                // - dotted breve:                           \db  \d9
                // - breve:                                   \b   \9  9
                // - double-dotted semibreve:                \Dw  \D8
                // - dotted semibreve:                       \dw  \d8
                // - semibreve:                               \w   \8  8
                // - double-dotted minim:                    \Dh  \D7
                // - dotted minim:                           \dh  \d7
                // - minim:                                   \h   \7  7
                // - double-dotted crotchet:                 \Dq  \D6
                // - dotted crotchet:                        \dq  \d6
                // - crotchet:                                \q   \6  6
                // - double-dotted quaver:                   \De  \D5
                // - dotted quaver:                          \de  \d5
                // - quaver:                                  \e   \5  5
                // - double-dotted semiquaver:               \Dx  \D4
                // - dotted semiquaver:                      \dx  \d4
                // - semiquaver:                              \x   \4  4
                // - double-dotted demisemiquaver:           \Dt  \D3
                // - dotted demisemiquaver:                  \dt  \d3
                // - demisemiquaver:                          \t   \3  3
                // - double-dotted hemidemisemiquaver:       \Di  \D2
                // - dotted hemidemisemiquaver:              \di  \d2
                // - hemidemisemiquaver:                      \i   \2  2
                // - double-dotted semihemidemisemiquavers:  \Dn  \D1
                // - dotted semihemidemisemiquavers:         \dn  \d1
                // - semihemidemisemiquavers:                 \n   \1  1
                // - 256th:                                   '!'
                // - 512th:                                   '`'
                // - 1024th:                                  '~'
                ,

                [67, \q, \f]
                // The three-element array containing a scientific pitch name followed by
                // a duration then a dynamic mark defines the pitch of the note, its duration and dynamic mark of
                // the note.
                ,

                [67, \q, \s2],
                // Strong dynamic marks can be abbreviated as follows:
                // - ffff: s4
                // - fff:  s3
                // - ff:   s2
                // - f:    s1   <- to distinguish it with a stacatto symbol (s).

                [67, \q, \q3],
                // Quiet dynamic marks can be abbreviated as follows:
                // - pppp: q4
                // - ppp:  q3
                // - pp:   q2
                // - p:    q1   <- to distinguish it with a crotchet symbol (q).


                [68, \e, \pp, \A]
                // The four-element array containing a scientific pitch name followed by
                // a duration, a dynamic mark and an articulation defines the pitch of the note, its duration,
                // dynamic mark and articulation. The following articulations are ready to use:
                //
                // Articulations      Symbols
                // - accent:          \a
                // - strong-accent:   \A
                // - staccato:        \s
                // - staccatissimo:   \S
                // - tenuto:          \o
                // - detached-legato: \O
                ,

                [68, \e, \s2, \a, \j]
                // The five-element array containing a scientific pitch name followed by
                // a duration, a dynamic mark, an articulation and a tie status defines the pitch of the note,
                // its duration, dynamic mark, articulation and tie status.
                // The following tie states are ready to use:
                //
                // Tie status                       Symbols
                // - tie start:                       \j <- from 'join'
                // - tie stop:                        \J
                // - laissez vibrer:                  \r <- from 'let-ring'
                // - tie stop with laissez vibrer:    \R
                ,

                [68, \e, \s2, \J],

                [68, \s, \s2, \r],

                [68, \x, \j],

                [68, \e, \R],

                [68, \e, \s2, \S, \r, \u]
                // The six-element array containing a scientific pitch name followed by
                // a duration, a dynamic mark, an articulation, tie status a slur status defines
                // the pitch of the note, its duration, dynamic mark, articulation, tie status and slur status.
                // The following slur states are ready to use:
                //
                // Slur status                       Symbols
                // - slur start:                       \u <- from r in 'slur'
                // - slur stop:                        \U
                ,


                [69, \p]
                // The elements of an entry can be flexibly omitted.
                // The two-element array containing a scientific pitch name followed by an dynamic mark defines
                // the pitch of the note with its dynamic mark,
                // and its duration follows the duration of the previous note
                ,

                [71, \p]
                // The repetition of the dynamic mark will not be displayed on the score, except for sf.
                ,

                [72, \sf],

                [73, \sf],

                [74, \pp, \A, \r]
                ,

                [74, \s]
                // The two-element array containing a scientific pitch name followed by
                // an articulation defines the pitch of the note with its articulation,
                // and its duration and dynamic mark follow those of the previous note.
                ,

                [$|, \S]
                // The array containing $| defines that the pitch of the note is a repetition of
                // the previous one. Its duration and length follow those of the previous note.
                ,

                [[74, 75]],

                [$|, \h, \f, \O],

                [$|, \p, \o],

                [$|, \U],

                [$\\]
                // The array containing $\\ defines that the entry is a repetition of the previous entry.
                ,

                [[\c5, \a4, \fs4, \ds4], \x, \f],

                [$\\, \s],

                [$\\],

                [$\\],

                [$|, \7],
            ]
        ),
        p2: (
            lbl: \Contralto,
            atr: (key: [0, \none], time: \x, staves: 1, clef: [[\c, 3]]),
            v1: [
                [[\c5, \a4, \fs4, \ds4], \_, \ffff],
                [[\cq5, \aq4, \fQ4, \dQ4], \d_, \fff],
                [[\df5, \bf4, \g4, \e4], \D_, \ff]
            ]
        ),
        p3: (
            lbl: \Tenore,
            atr: (key: [0, \none], time: \x, staves: 1, clef: [[\c, 4]]),
            v1: [
                [[63.5, 60.5, 57.5, 54.5], \D0, \f],
                [[64, 61, 58, 55], \d0, \mf],
                [[64.5, 61.5, 58.5, 55.5], \0, \sf]
            ],
            v2: [
                [\r, \D0],
                [\r, \d0],
                [\r, \0]
            ]
        ),
        p4: (
            lbl: \Basso,
            atr: (key: [0, \none], time: \x, staves: 1, clef: [[\f, 4]]),
            v1: [
                [\r, \D9],
                [\r, \d9],
                [\r, 9]
            ],
            v2: [
                [[52, \g3], \D9, \mp],
                [[\e3, 55], \d9, \p],
                [[52.5, \gQ3], 9, \pp],
                [[\eq3, 56.5]]
            ]
        ),
        p5: (
            lbl: 'Clarinetto in si bemolle',
            atr: (key: [0, \none], time: \x, staves: 1, clef: [[\g, 2]], trans:[-1, -2, 0]),
            v1: [
                [72, \D8],
                [\cq5, \d8],
                [73, 8],
                [\cQ5]
            ],
            v2: [
                [67, \D8],
                [\gq4, \d8],
                [68, 8],
                [\gQ4]
            ]
        ),
        p6: (
            lbl: 'Violino',
            atr: (key: [0, \none], time: \x, staves: 1, clef: [[\g, 2]]),
            v1: [
                [72, \D8],
                [\cq5, \d8],
                [73, 8],
                [\cQ5]
            ],
            v2: [
                [67, \D8],
                [\gq4, \d8],
                [68, 8],
                [\gQ4]
            ]
        ),
        p7: (
            lbl: \Viola,
            atr: (key: [0, \none], time: \x, staves: 1, clef: [[\c, 3]]),
            v1: [
                [\cq4, \D7, \ppp],
                [\dq4, \d7, \pppp],
                [[61.5, \dq4, \cq4], 7]
            ]
        ),
        p8: (
            lbl: \Violoncello,
            atr: (key: [0, \none], time: \x, staves: 1, clef: [[\f, 4]]),
            v1: [
                [\c3, \D6, \f],
                [\c3, \d6, \f],
                [\c3, 6, \f]
            ]
        ),
        p9: (
            lbl: \Contrabass,
            atr: (key: [0, \none], time: \x, staves: 1, clef: [[\f, 4]], trans: [0, 0, -1]),
            v1: [
                [\c3, \D6, \f],
                [\c3, \d6, \f],
                [\c3, 6, \f]
            ]
        ),
        p10: (
            lbl: 'Piano 1',
            atr: (key: [0, \none], time: \x, staves: 2, clef: [[\g, 2], [\f, 4]]),
            v1: [
                [[\c5, \e5, \g5], \D5, \f],
                [\ef5, \d5],
                [\c5, \5],
                [$|],
                [$|, \D4],
                [$|, \d4],
                [$|, \4],
                [[\d5, 76], \D3],
                [$|, \d3],
                [$|, \3],
                [[\d4, 65, 68], \D2],
                [$|, \d2],
                [$|, \2]/*,
                [[69, 74, 88], \D1, \sf],
                [$|, \d1],
                [$|, \1],
                [$|, '!'],
                [$|, '`'],
                [$|, '~']*/
            ],
            v2: [
                [\cF4, \h],
                [\cf3, \h]
            ]
        ),
        p11: (
            lbl: 'Piano 2',
            atr: (
                key: [0, \none],
                time: \x,
                staves: 4,
                clef: [[\g, 2, 2], [\g, 2], [\f, 4], [\f, 4, -2]]
            ),
            v1: [
                [[\a7, \a6], [\e, 3, 2, \e]],
                [$|, [\e]],
                [$|, [5, \]],
                [[\af7, \as6], [\e, 3, 2, \e]],
                [$|, [[\x, 3, 2, \x]]],
                [$|, [[\x]]],
                [$|, [[\x, \]]],
                [$|, [\e, \]],
                [$|, [\e, 5, 4, \e]],
                [$|, [[\e, 3, 2, \e]]],
                [$|, [[\q, \]]],
                [$|, [\q, \]]
            ],
            v2: [
                [[\a5, \a4], [\x, 5, 4, \x]],
                [$|, [\x]],
                [$|, [4]],
                [$|, [\x]],
                [$|, [4, \]],
                [[\af5, \a4], [\x, 6, 4, \x]],
                [$|, [\x]],
                [$|, [5]],
                [$|, [\x]],
                [$|, [4, \]]
            ],
            v3:
            [
                [[\a3, \a2], 6]
            ],
            v4: [
                [[\a1, \a0]]
            ],
        )
    )
];

~exportXML.(~score1, "~/Downloads/multiple instruments.musicxml".standardizePath, 'MuseScore 4')
)

3. Example 2: simple tuplets

(
~score2 = [
    (   title: 'untitled', composer: 'me', rights: '©'),
    (
        bar: 1,
        p1: (
            lbl: \,
            atr: (
                key: [1, \major],
                time: [4, 4],
                staves: 4,
                clef: [[\g, 2, 2], [\g, 2], [\f, 4], [\f, 4, -2]]
            ),
            v1: [
                [\t, 120],
                [\q4],
                [96],
                [[\a7, \g7], \dh],
                [\a6, [\e, 3, 2, \e], \pppp],
                [$|, [\e], \s4],
                [$|, [5, \]],
                [\r, \q],
                [\r, \h]
            ],
            v2: [
                [\a4, [\x, 5, 4, \x]],
                [$|, [\x]],
                [$|, [4]],
                [$|, [\x], \sf],
                [$|, [4, \], \sf],
                [\r, \dh]
            ],
            v3: [
                [\a3, 6],
                [\a3, \sf],
                [\r, \dh]
            ],
            v4: [
                [\a0],
                [\a0, \p],
                [\a0, \mf],
                [\r, \dh]
            ]
        )
    ),
    (
        bar: 2,
        p1: (
            v1: [
                [\s1],
                [\g6, [\e, 3, 2, \e]],
                [$|, [\e]],
                [$|, [5, \]],
                [\r, \dh]
            ],
            v2: [
                [\a4, [\x, 5, 4, \x]],
                [$|, [\x]],
                [$|, [\x]],
                [$|, [\e, \]],
                [\r, 5],
                [\r, \q],
                [\r, \h]
            ],
            v3: [
                [\a3, \d6],
                [\r, \q]
            ],
            v4: [
                [\a0],
                [\r, \dh]
            ]
        )
    )
];

~exportXML.(~score2, "~/Downloads/simple tuplets.musicxml".standardizePath, 'MuseScore 4')
)

4. Example 3: 12-tone matrix generation and playback

(
~matrix_12tone = { |array|
    var matrix = 12.collect { |i| array[i]; array - (array[i] % 12) };
    matrix.do { |item| item.replace(11, \e).replace(10, \t).postln }
};

~matrix = ~matrix_12tone.((0..11).scramble) + 72;

~score3 = [(title: '12-tone series matrix', composer: 'randomised', right: '©')] ++
~matrix.collect { |series, index|
    if (index == 0) {
        (
            bar: index + 1,
            p1: (
                lbl: '',
                atr: (key: [0, \none], time: \x, staves: 1, clef: [[\g, 2]]),
                v1: series.collect { |aNote| [aNote, \w] }
            )
        )
    } {
        series.postln;
        (
            bar: index + 1,
            p1: (v1: series.collect { |aNote| [aNote, \w] })
        )
    }
};

fork{
    ~score3[1..].do { |amusicXMLelement|
        var bar, notes;
        bar = amusicXMLelement[\bar];
        notes = amusicXMLelement[\p1][\v1];
        notes.do { |noteOrchord|
            var pitch, duration;
            # pitch, duration = noteOrchord;
            duration = (w: 0.4/*, h: \w / 2, q: \w / 4*/)[duration];
            ("bar" + bar ++ ":" + noteOrchord).postln;
            (midinote: pitch, dur: duration).play;
            duration.wait
        }
    }
};

~exportXML.(~score3, "~/Downloads/12-tone series matrix.musicxml".standardizePath, 'MuseScore 4')
)

5. Example 4: Creating and playing a random composition for an instrument

(
var metronome, notesOrRests, dynamics, noteTypes, title = 'random an instrument';
metronome = 240;
notesOrRests = { [{ ((21..108)).choose } ! (1..4).choose, \r].choose } ! 100; // \r: rest
// <- ! 100: increasing the number can results in the following error:
// Interpreter has crashed or stopped forcefully. [Exit code: 11]

dynamics = (ffff: 115, fff: 103, ff:91, f:79, mf: 67, mp: 55, p: 43, pp: 31, ppp: 19, pppp: 7, sf: 127);
noteTypes = (/*Dm: 56, dm: 48, m: 32, Dl: 28, dl: 24, l: 16, Db: 14, db: 12, b: 8, Dw: 7, dw: 6,*/
    w: 4, Dh: 3.5, dh: 3, h: 2, Dq: 1.75, dq: 1.5, q: 1,
    De: 0.875, de: 0.75, e: 0.5, Dx: 0.4375, dx: 0.375, x: 0.25/*, Ds: 0.021875, ds: 0.1875, s: 0.125,
Df: 0.109375, df: 0.09375, f: 0.0625*/);

~score4 = [
    (title: title, composer: 'randomised', right: '©'),
    (
        bar: 1,
        p1: (
            lbl: '',
            atr: (key: [0, \none], time: \x, staves: 4, clef: [[\g, 2, 2], [\g, 2], [\f, 4], [\f, 4, -2]]),
            v1: []
        )
    )
];

notesOrRests.do { |aNotesOrRest, index|
    var musicXMLelement = switch (aNotesOrRest.class.asSymbol, // symbol: rest, array: notes
        \Array,    { [aNotesOrRest, noteTypes.keys.choose, dynamics.keys.choose] },
        \Symbol, { [aNotesOrRest, noteTypes.keys.choose] }
    );
    ~score4[1][\p1][\v1] = ~score4[1][\p1][\v1].add(musicXMLelement)
};

~score4[1][\p1][\v1] = ~score4[1][\p1][\v1].insert(0, [\t, metronome]);

~exportXML.(~score4, ("~/Downloads" +/+ title ++".musicxml").standardizePath, 'MuseScore 4');

fork{
    (1 .. ~score4[1][\p1][\v1].size - 1).do { |index|
        var thisXMLelement, aNoteOrChordOrRest, duration, velocity;

        thisXMLelement = ~score4[1][\p1][\v1][index];

        (    "\n" ++ index ++ ":" + thisXMLelement + thisXMLelement.class).postln;

        if (thisXMLelement.size == 3) {
            # aNoteOrChordOrRest, duration, velocity = thisXMLelement
        } {
            if (thisXMLelement[0] == \r) {
                # aNoteOrChordOrRest, duration = thisXMLelement;
            }
        };
        (    "\taNoteOrChordOrRest:" + aNoteOrChordOrRest + aNoteOrChordOrRest.class).postln;
        (    "\tvelocity:" + velocity + velocity.class).postln;
        duration = noteTypes[duration];
        velocity = dynamics[velocity];
        if (velocity == nil) { velocity = 0 };
        (    "\tvelocity to amp:" + velocity + velocity.class).postln;
        (    "\tduration:" + duration + duration.class).postln;
        (midinote: aNoteOrChordOrRest, amp: velocity.linlin(0, 127, -48, -12).dbamp).play;
        (duration / (metronome / 60)).wait;
    }
}
)

6. Example 5: Creating and playing a random composition for two instruments

(
var metronome, makeNotesOrRest, dynamics, noteTypes, makeDynamic, makeNotetype, title = 'random two instruments';
metronome = 120;
makeNotesOrRest = {|lowest, highest| [{ ((lowest..highest)).choose } ! (1..4).choose, \r].choose }; // \r: rest
dynamics = (ffff: 115, fff: 103, ff:91, f:79, mf: 67, mp: 55, p: 43, pp: 31, ppp: 19, pppp: 7, sf: 127);
makeDynamic = { dynamics.keys.choose };
noteTypes = (/*Dm: 56, dm: 48, m: 32, Dl: 28, dl: 24, l: 16, Db: 14, db: 12, b: 8, Dw: 7, dw: 6,*/
    w: 4, Dh: 3.5, dh: 3, h: 2, Dq: 1.75, dq: 1.5, q: 1,
    De: 0.875, de: 0.75, e: 0.5, Dx: 0.4375, dx: 0.375, x: 0.25/*, Ds: 0.021875, ds: 0.1875, s: 0.125,
Df: 0.109375, df: 0.09375, f: 0.0625*/);
makeNotetype = { noteTypes.keys.choose };

~score5 = [
    (title: title, composer: 'randomised', right: '©'),
    (
        bar: 1,
        p1: (
            lbl: '',
            atr: (key: [0, \none], time: \x, staves: 1, clef: [[\g, 2]]),
            v1: []
        ),
        p2: (
            lbl: '',
            atr: (key: [0, \none], time: \x, staves: 1, clef: [[\f, 4]]),
            v1: []
        )
    )
];

100.do
// <- 100.do
// : increasing the number can results in the following error:
// Interpreter has crashed or stopped forcefully. [Exit code: 11]
{
    var makeMusic = { |lowest, highest|
        var    musicXMLelement = makeNotesOrRest.(lowest, highest);
        switch (musicXMLelement.class.asSymbol, // symbol: rest, array: notes
            \Array,    { [musicXMLelement, makeNotetype.(), makeDynamic.()] },
            \Symbol, { [musicXMLelement, makeNotetype.()] }
        )
    };
    ~score5[1][\p1][\v1] = ~score5[1][\p1][\v1].add(makeMusic.(60, 84));
    ~score5[1][\p2][\v1] = ~score5[1][\p2][\v1].add(makeMusic.(36, 60));
};

~score5[1][\p1][\v1] = ~score5[1][\p1][\v1].insert(0, [\t, metronome]);

~exportXML.(~score5, ("~/Downloads" +/+ title ++".musicxml").standardizePath, 'MuseScore 4');

~playMusic = { |player, pan, tab = ""|
    fork {
        (~score5[1][player][\v1].size).do { |index|
            var thisXMLelement, aNoteOrChordOrRest, duration, velocity;

            thisXMLelement = ~score5[1][player][\v1][index];

            (    "\n" ++ tab ++ index ++ ":" + thisXMLelement + thisXMLelement.class).postln;

            if (thisXMLelement[0] != \t) {
                if (thisXMLelement.size == 3) {
                    # aNoteOrChordOrRest, duration, velocity = thisXMLelement
                } {
                    if (thisXMLelement[0] == \r) {
                        # aNoteOrChordOrRest, duration = thisXMLelement;
                    }
                };
                (    tab ++ "\taNoteOrChordOrRest:" + aNoteOrChordOrRest + aNoteOrChordOrRest.class).postln;
                (    tab ++ "\tvelocity:" + velocity + velocity.class).postln;
                duration = noteTypes[duration];
                velocity = dynamics[velocity];
                if (velocity == nil) { velocity = 0 };
                (    tab ++ "\tvelocity to amp:" + velocity + velocity.class).postln;
                (    tab ++ "\tduration:" + duration + duration.class).postln;
                (midinote: aNoteOrChordOrRest, amp: velocity.linlin(0, 127, -36, -12).dbamp, pan: pan).play;
                (duration / (metronome / 60)).wait;
            }
        }
    }
};

~playMusic.(\p1, -0.6);
~playMusic.(\p2, 0.6, "\t\t\t\t\t\t")
)





2 Likes

Impressive. I’m looking forward to playing with this!

I am curious why you built this as a function rather than a class?

@semiquaver
I am glad you responded positively.

The reason I wrote it as a function is due to the following reasons:

  1. Writing and evaluating it does not require recompiling, so I think it can save time.

  2. I do not think the code structure is optimised in managing CPU peaks and memory usage, nor is it perfect in its logic. I think it is better to start writing it as a class when I have confidence in its functionality from all of those perspectives.

  3. Besides writing the function as a class, one or more methods should be added to existing classes. At least the following function extracted the function above in this thread should be implemented as a method .midispn (MIDI number to scientific ptch notation) for simple numbers and arrays, even though both MIDI and SPN basically only support integer and twelve-tone equal temperament (I can’t think of a better name than .midispn):
    A class or method to convert integer and float to scientific pitch notation?

  4. Moreover, there should be a common convention for notating music notation using sclang, at least agreed upon by developers before writing a musicXML class.

  5. One of the core developers @josh has already written some classes for musicXML. Wouldn’t it be better if @josh updated his classes with references to the code components of my function? There are already a lot of quarks, and releasing more quarks is messy.

I think musicxml support is very important and should be included in future versions of the official SuperCollider releases, not as a quark or anything else, for the following reasons

  1. using SuperCollider in the classroom is tedious for students without pre-experience in coding and having to install extra packages for them takes time and energy. Until a few years ago I installed all available quarks and class libraries if sc did not crash when compiling the libraries. At the moment I use the classes as little as possible outside the SuperCollider primitives to avoid conflict with my students who start with only official releases of SC. (I think many useful classes should be included in the sc primitives.)

  2. approaching computer-aided composition or digital sound production from the interfacing music notation with the computer science is easy to explain the related theory and fun to practice.

I do not know how other developers and users feel about this. The long-term users of SC, apart from myself, are very professional in programming. However, most beginners and many musicians interested in digital sound or computer-aided composition are not so professional in programming. Wouldn’t it be great if sclang could provide an easy way for people interested in this area to get started?

In the hope that many users will take a look at my function and leave feedback.

2 Likes

Please submit a PR to the stuff I’ve already done - more than happy to have your contribution!

2 Likes

I would gladly do it.
First, I should analyse your code. At first glance, your approach and mine seem quite different. I cannot do it immediately. Also, I should urgently do something else by August. I think I can do it a few months later after getting some feedback from other users. I will leave another message when I do something related to this.

1 Like

The musicxml format is quite complex, I just don’t think it is suitable for this. But then again, music notation is also a very complex language with many edge cases. A simpler intermediate format might be better, but it would have to be a subset of normal notation to keep it’s simplicity. In a way, a reduced lilypond might be easiest as it makes note names and durations obvious.

Regarding sc in general, what would be really nice is red squiggles under errors before you execute the code… but I doubt that will ever happen. Perhaps clearer error messages would be better. The current stack dump is very computer sciencey and not at all clear to understand. It would also be nice to be able to inspect there error in more detail, perhaps saving the error to a variable and asking it for more information if needed. I don’t know how that could work though.

1 Like

My intention is not to make users struggle with musicxml. The codes like 5 examples are what the users will deal with: the events and array of sclang.

The current messages are for debugging. They will be removed later, and the core part of the messages will remain to inform users of their errors.

My goal is as follows:

If users follow the notation instruction correctly, they only need to correct the code by comparing the code and the score in their preferred notation editor.

@smoge Have you tried my function and 5 examples?
You are one of the few people interested in this kind of music. I would appreciate your thoughts on creating musicXML using array and events and playing it via scserver.
Your opinion would be invaluable as I try to develop the implementation of this function as part of the sclang library!

1 Like

I have made a Quark libray related to this thread. It seems to work with the examples given, but needs further work.

Detailed help documents are provided for each class, as well as a Notator score guide!

2 Likes

While testing with a SC without SC3 plugins and without Quark, I found some problems and fixed them. I feel that this kind of work is not popular among SC users, but I upload it this way. I hope someone with good programming skills will take a close look at it.

@Newton_Armstrong @smoge

Hi there,
I understand that you both have implemented the LilyPond feature in sclang to enable music notation in SuperCollider.
At the moment there is a lot of discussion about SC4 or the development of the current version of SC. I think this is a good time to start implementing the music notation feature and exporting musicXML files from SuperCollider. I would have implemented this in the core library, but the main idea of the users involved in the development would make a better Quarks system for such features.

How it is done is not a big issue.

Are you interested in doing this together? Your LilyPond implementations are already great, but it would be more convenient not to use LilyPond, since learning SuperCollider is already not easy for many students, and learning another programming language at the same time is not easy. What I have done is the first draft of this, in which you can get a musicXML file for custom music notation software, and get standard rewritten sclang code to play music using scserver, simply by constructing musical content using arrays and events.

My intention is not to implement the whole engraving system, but to implement a basic feature to work seamlessly between SuperCollider and any music notation software.
I think it would provide a more convenient workflow and environment, and be practical when using SuperCollider to have a consistent and well-defined way of creating music notation.

If both of you, or either of you, and of course anyone else who reads this post is interested, please write your opinion. If you think this is not a necessary feature of SuperCollider, please let me know! Thanks for reading!

1 Like