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")
)