Musical Notation in SC using musicXML

Dear users and developers,

What is currently the easiest way to read and write musicXML files in SC under Linux, MacOS and Windows? I think writing musicXML files is more important.

The following can be found on the old sc-user mailing list and the active scsynth forum:

After some tests with sample code provided by the developers, I think that

  1. LilyCollider seems to be too old.
  2. LilyPonds does not natively import and export musicXML.
  3. Fosc seems to work, but I cannot find an example of musicXML import/export.
  4. Superfomus seems to generate musicXML files, but the core part Fomus seems to be incompatible with modern MacOS. (tested with Ventura on MBP 2018 i9 and with Monterey on MBP 2021 m1max).
  5. XMLNote seems to work.

I think notation via Lylipond is not necessary if sclang supports musicXML export.
Could you share your experiences, not from the deepest developer’s point of view, but from the normal user’s point of view?

XML quark is the only thing I’ve used.

I HATE Lilypond. It seems useful for times when you need graphic notation or you have to break your notation program frequently but it took me about an hour to have enough information to notate one measure of my half-page hand written score, thinking I could learn enough in the process to be comfortable with it.

SC was worth learning. C++ was worth learning. Lilypond seems like a massive headache.

Sorry for the digression and not trying to hijack the thread. I’m also just kind of curious what others might think.

To leap to lilypond’s defence…

Example of the first page of the last thing I made in Lilypond. It is a baroque flute with video.
The entire thing is made in lilypond, no graphics program.

Basically I extend lilypond using scheme to make this sort of tab super easy to work with.

The following does finger one-closed, and two-half-closed for a dotted minim, then has a transition line to both fingers open.

<oneC twoH>2.\fltabKTrans  <oneO twoO>4

The curve pitch/harmonic line is done like:

a4.\curveExp b'8\fltabHHold

Curves are all bezier, and \curveExp is just a predefined one, its easy to make new shapes, they also split across the system break as you would expect them to.

Getting lilypond setup to do this was a nightmare… I HATE scheme.

But using it to quickly make changes is a breeze, as everything is programmatically engraved.

In Sibelius (which I have quite a lot of experience using) it is basically the other way around, getting the simple stuff is easy, editing and making sure everything looks good is a nightmare as the program has a habit of ‘throwing’ your graphics across the page when you add a new bar.

This also means you can quickly change the layout of the document

2 Likes

I remember, with Finale, that it took a lot of mouse tweaking to get fundamental notational competence out of it.

Whereas with LilyPond, the default output is already very very close to professional, for standard notation. I actually once thought a colleague in a recording session was looking at a printed classical score, but it was my piece – the proportions, weights, spacing etc were all so close to printed volumes that, at a brief glance, I didn’t recognize it as computer notation.

When I learned it, I had a good model to follow, showing how to write parts as separate variables, and assemble these into the score – opposite of graphical notation programs’ approach, where you split a score into parts. I think I wouldn’t have guessed that on my own, but it set me on the right path with it.

YMMV of course – and I’d be the first to agree that LilyPond is terrible at score editing – but it promises good results with minimal tweaks, and (for me at least) it delivered. Haven’t written a score in a long time but I would use LP again.

hjh

I used Finale for a long time. Occasionally I used Sibelus for convenience, but I stuck with Finale. From 2011 to mid-2018, I had less time to compose for instruments, and when I started writing music again, I could not understand Finale’s way of notating music, which I had always taken for granted. At that time, I compared many notation software programs, including MuseScore, StaffPad, Forte, Notion, etc., but I compared Finale, Sibelius and Dorico intensively and then chose Dorico. For me, Dorico is the most composer and arranger friendly.

As for LyliPond, I have occasionally tested the way it works by coding. It is surprisingly attractive. So I considered using LyliPond instead of Dorico in 2018. However, it was extremely tedious to install, and there was almost always a platform compatibility problem: no usable binary, and building a binary from source was not straightforward.

I have occasionally tried to automatically transfer the algorithmically generated notes, including pitches with duration and dynamic markings. I tried using MIDI or LyliPond, but now I think using musicXML would be the easiest way, but using musicXML in SuperCollider seems not so easy, so I posted this question. I have also seen some web documents about LaTex and musicXML with Python etc.
Now, I want to use tools that works easily and well maintained.

@mjsyts, where can I find the usage examples with code? It is already installed on my end, but I could not find any examples.

I’ve switched to music21 python library which is extremely cool and integrable with e.g. Musescore. Of course, you need python. But if you generate data from SC then it would be easy to write a small program in python using music21
Best
-a-

For generating musicxml from python (e.g. for using in combination with musescore) I would recommend taking a look at scamp (http://scamp.marcevanstein.com/) instead of music21. You can also combine both if you want to use the music theory stuff provided by music21. Last time I checked, music21 didn’t support musicxml directly. Musci21 does support generating lilypond, so depending on what you need you can choose what suits you best.

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