A class or method to convert integer and float to scientific pitch notation?

Euterpea is a great source to learn, the book is also very good. But unfortunately, it would not be trivial to implement any kind of microtones with it.

1 Like

Yeah too bad. Mainly sharing because it’s related to Haskell & notation. It’s been ages since I used it. Maybe some of the higher level types are polymorphic enough to reuse?

1 Like

It’s not too hard to build a parser that converts note names into frequencies or ratios. But at some point, someone’s probably going to want to transpose notes and convert them back to music notation while handling accidentals correctly (both for readability in sheet music and correctness in case of non-12edo tunings), so you’ll need a pitch encoding layer in between note name strings and frequencies. There are multiple microtonal paradigms, so that pitch encoding layer may need to be extensible and modular depending on your needs.

I hope this doesn’t come off as condescending but I’ve worked on a (now defunct) music notation project and had many conversations with the Abjad guys, and in my experience music encoding is a challenging field. Be aware of what you’re getting into when designing these systems. Good luck!

2 Likes

Yes, I think there are many great ideas there. The :+: and :=: operators are interesting, since they construct a kind of tree data type, with vertical and horizontal leaves. It’s very appropriate to represent music scores.

data Orientation = H | V
  deriving (Show)

data OrientedTree a
  = Val a
  | Group Orientation [OrientedTree a]
  deriving (Show, Functor)

But the pitch level has a more simple approach. For example, the relation between pitches and intervals would fit very well with a vector space type.

2 Likes

@nathan @VIRTUALDOG @smoge
Thanks for your valuable opinions. I asked this question to be able to use these classes and methods, if they exist, to create music scores simply by constructing subclasses of the collection class, as introduced in the following post (One problem is that musicXML only seems to support quarter-tones at the moment. This would exclude other microtonal accidentals, although I would like to extend it at least up to sixteenth-tones.):

@semiquaver @jordan @josh

I have updated the methods suggested above a bit, added a new class SPN, and done my best to cover all your opinions as best I can:

  • Now sclang returns pitch names as an array containing all available pitch names. Please let me know if I missed anything.
  • SPN has three options for displaying the pitch name: \lily, \mus, and \ez. The default is \lily.
  • PitchClass is already in @josh’s Ctk Quarks, but my new methods for this class do not conflict with it. Also, my PitchClass supports quartertones. I would appreciate your opinion. I have not made a PR as I think it should be discussed before it is made.

@semiquaver
Thanks for pointing this out!

Yes! Thus, I implemented various notations!

Scientific pitch notation is a verbal expression, so the use of the Roman numeral to indicate the steps of a scale has nothing to do with it.
Anyway, what does 3 mean in .inkey('c#', \minor, 3)? Is it the octave number? Normally, a key name starting with an upper-case letter indicates a major key, and a key name starting with a lower-case letter indicates a minor key. So the second argument in .inkey() is not necessary if .inKey is only related to major-minor tonality. I have not been able to find the .inKey method, so I cannot say more at this time.

Using \a3 in events without .cps is a really good idea.

@jordan

  1. I have included LilyPond’s format for those users who are already used to LilyPond’s notation, although I am not sure how many SC users (especially beginners) are used to LilyPond’s pitch notation. I deliberately did not use LilyPond style pitch notation because it uses multiple characters for one accidental. The English naming system in LilyPond is not intuitive to me. The German naming system in LilyPond is very intuitive to me, but it is too long. The following are my own inventions (they are simpler than the LilyPond style):

    1. Example 1 (currently used):
      • S: double-sharp
      • Q: three quarter-tone raised
      • s: sharp
      • q: quarter-tone raised
      • n: natural ← can be omitted
      • u: quarter-tone lowered is not used
      • f: flat
      • U: three quarter-tone lowered is not used
      • F: double-flat
    2. Example 2:
      • S: double-sharp
      • R: three quarter-tone raised
      • r: sharp
      • n: quarter-tone raised
      • o: natural ← can be omitted
      • l: quarter-tone lowered
      • f: flat
      • L: three quarter-tone lowered
      • F: double-flat
    3. Example 3:
      • S: double-sharp
      • M: three quarter-tone raised
      • s: sharp
      • m: quarter-tone raised
      • o: natural ← can be omitted
      • w: quarter-tone lowered
      • f: flat
      • W: three quarter-tone lowered
      • F: double-flat
    4. Example 4:
      • M: double-sharp
      • m: three quarter-tone raised
      • N: sharp
      • n: quarter-tone raised
      • o: natural ← can be omitted
      • v: quarter-tone lowered
      • V: flat
      • w: three quarter-tone lowered
      • W: double-flat
  2. There are different systems for naming pitches.
    In wslib’s cpsname, the middle A (440 Hz) is A3, while the ‘Scientific Pitch Notation’ defines it as A4. In South Korea, I am used to 440 Hz as A3 with the earlier generations involved in computer music, A4 in the group of musicians and music theorists as well as your generation in the computer music field, and in Germany as a' or a1. I think A4 is generally easier for most musicians, especially in English-speaking cultures and perhaps most of the world. In computer music, A3 seems to have been widely used for 440 Hz before, but I see that many software programs offer both names (A3 and A4) according to the user’s preference. I am also familiar with Helmholz pitch notation, but this would not be considered.

@josh
In the first draft, I omitted all pitch names with flats because they are redundant under some conditions. Under other conditions, however, they are very important. Under other conditions double flats and double sharps, even triple sharps and triple flats should be considered. I just wanted to simplify these things…

I think such pitch names should be added if some users or developers think they should be there. So I did it. If I just add all the flat names, someone might ask how to get double-flatted pitch and double-sharped pitch etc. So I upload a version that includes all possibilities (N.B.: This time I also used musical symbols to improve readability, but I am not sure if it really improves readability.):

Overview of use:
(The definitions of the methods and classes are at the bottom of the post.)

\df4.midi // -> 61.0
\cs4.midi // -> 61.0
\df4.cps  // -> 277.18263097687
\cs4.cps  // -> 277.18263097687

PitchClass.lily[1] // -> [ cs, df, bx ]
PitchClass.mus[1]  // -> [ c♯, d♭, b𝄪 ]
PitchClass.ez[1]   // -> [ cs, df, bS ]
1.pitchClass // -> [ cs, df, bx, c♯, d♭, b𝄪, cs, df, bS ]

PitchClass.lily[\t] // -> [ as, bf, cff ]

PitchClass.lily['t.5'] // -> [ bqf, atqs, ctqf, cffqs ]
PitchClass.lily[\t5]   // -> [ bqf, atqs, ctqf, cffqs ]


0.midispn    // -> [ c-1, bs-2, dff-1 ]
'c-1'.midi   // -> 0.0
\cm1.midi    // -> 0.0
'bs-2'.midi  // -> 0.0
'dff-1'.midi // -> 0.0
\bsm2.midi   // -> 0.0
\dffm1.midi  // -> 0.0

8.1757989156437.cpsspn // -> [ c-1, bs-2, dff-1 ]
'c-1'.cps              // -> 8.1757989156437
\cm1.cps               // -> 8.1757989156437
'bs-2'.cps             // -> 8.1757989156437
'dff-1'.cps            // -> 8.1757989156437
\bsm2.cps              // -> 8.1757989156437
\dffm1.cps             // -> 8.1757989156437

127.midispn        // -> [ g9, fx9, aff9 ]
127.midispn(\lily) // -> [ g9, fx9, aff9 ]
127.midispn(\mus)  // -> [ g♮9, f𝄪9, a𝄫9 ]
127.midispn(\ez)   // -> [ gn9, g9, fS9, aF9 ]

SPN.cps(440)        // -> [ a4, gx4, bff4 ]
SPN.cps(440, \lily) // -> [ a4, gx4, bff4 ]
SPN.cps(440, \mus)  // -> [ a♮4, g𝄪4, b𝄫4 ]
SPN.cps(440, \ez)   // -> [ an4, a4, gS4, bF4 ]

SPN.midi(69)        // -> [ a4, gx4, bff4 ]
SPN.midi(69, \lily) // -> [ a4, gx4, bff4 ]
SPN.midi(69, \mus)  // -> [ a♮4, g𝄪4, b𝄫4 ]
SPN.midi(69, \ez)   // -> [ an4, a4, gS4, bF4 ]

69.5.midispn        // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
69.5.midispn(\lily) // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
69.5.midispn(\mus)  // -> [ a ¼↑ 4, b♭ ¼↓ 4, a♯ ¼↓ 4, g𝄪 ¼↑ 4, b𝄫 ¼↑ 4, c𝄫 ¼↓5 ]
69.5.midispn(\ez)   // -> [ aq4, bU4, gSq4, bFq4, cFu5 ]

452.89298412314.cpsspn        // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
452.89298412314.cpsspn(\lily) // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
452.89298412314.cpsspn(\mus)  // -> [ a ¼↑ 4, b♭ ¼↓ 4, a♯ ¼↓ 4, g𝄪 ¼↑ 4, b𝄫 ¼↑ 4, c𝄫 ¼↓5 ]
452.89298412314.cpsspn(\ez)   // -> [ aq4, bU4, gSq4, bFq4, cFu5 ]

Post <<< PitchClass.lily
PitchClass.lily.class
PitchClass.lily.size
PitchClass.lily.do { |item| item.postln }

Post <<< PitchClass.mus
PitchClass.mus.class
PitchClass.mus.size
PitchClass.mus.do { |item| item.postln }

Post <<< PitchClass.ez
PitchClass.ez.class
PitchClass.ez.size
PitchClass.ez.do { |item| item.postln }

PitchClass.lily[0.0]
PitchClass.lily[0]
PitchClass.lily[0.5]
PitchClass.lily[1]

(
((0, 0.5 .. 9.5) ++ [10.0, \t, 10.5, \t5, 't.5', 11.0, \e, 11.5, \e5, 'e.5'] ++ (0..11)).do { |index|
	(index.asString ++ ":" + PitchClass.lily[index]).postln;
	(index.asString ++ ":" + PitchClass.mus[index]).postln; 
	(index.asString ++ ":" + PitchClass.ez[index]).postln;
	"".postln
}
)

(s.sampleRate / 2).cpsspn
(s.sampleRate / 2).cpsspn(\lily)
(s.sampleRate / 2).cpsspn(\mus)
(s.sampleRate / 2).cpsspn(\ez)

(
(100, 105 .. 230).sort.do { |index|
	(index.asString + "Hz:\tMIDI pitch                -" + index.cpsmidi ++ ";\n\t\tMIDI Pitch rounded at 0.5 -" + index.cpsmidi.round(0.5) + "\n\t\t\t\t   lily (default) -" + SPN.cps(index, \lily)).postln;
	("\t\t\t\t   music          -" + SPN.cps(index, \mus)).postln; 
	("\t\t\t\t   ez             -" + SPN.cps(index, \ez)).postln;
	"".postln
}
)

(
((0..127) ++ (0, 0.5 ..127)).sort.do { |index|
	("MIDI Pitch Number" + index.asString ++ ": (lily, default) -" + SPN.midi(index, \lily)).postln;
	("MIDI Pitch Number" + index.asString ++ ": (music)         -" + SPN.midi(index, \mus)).postln; 
	("MIDI Pitch Number" + index.asString ++ ": (ez)            -" + SPN.midi(index, \ez)).postln;
	"".postln
}
)

SPN.cps(261.6255653006)      // -> [ c4, bs3, dff4 ]
261.6255653006.cpsspn        // -> [ c4, bs3, dff4 ]
261.6255653006.cpsspn(\lily) // -> [ c4, bs3, dff4 ]
261.6255653006.cpsspn(\mus)  // -> [ c♮4, b♯3, d𝄫4 ]
261.6255653006.cpsspn(\ez)   // -> [ cn4, c4, bs3, dF4 ]

SPN.midi(60)           // -> [ c4, bs3, dff4 ]
60.0.midispn           // -> [ c4, bs3, dff4 ]
60.0.midispn(\lily)    // -> [ c4, bs3, dff4 ]
60.0.midispn(\mus)     // -> [ c♮4, b♯3, d𝄫4 ]
60.0.midispn(\ez)      // -> [ cn4, c4, bs3, dF4 ]

261.6255653006.cpsmidi // -> 60.0
\c4.midi               // -> 60.0
\bs3.midi              // -> 60.0
\dff4.midi             // -> 60.0
'c♮4'.midi             // -> 60.0
'b♯3'.midi             // -> 60.0
'd𝄫4'.midi             // -> 60.0
\cn4.midi              // -> 60.0
\c4.midi               // -> 60.0
\bs3.midi              // -> 60.0
\dF4.midi              // -> 60.0

60.midicps // -> 261.6255653006
\c4.cps    // -> 261.6255653006
\bs3.cps   // -> 261.6255653006
\dff4.cps  // -> 261.6255653006
'c♮4'.cps  // -> 261.6255653006
'b♯3'.cps  // -> 261.6255653006
'd𝄫4'.cps  // -> 261.6255653006
\cn4.cps   // -> 261.6255653006

277.18263097687.cpsspn // -> [ cs4, df4, bx3 ]
\cs4.cps               // -> 277.18263097687
\df4.cps               // -> 277.18263097687
\bx3.cps               // -> 277.18263097687

61.0.midispn // -> [ cs4, df4, bx3 ]
\cs4.midi    // -> 61.0
\df4.midi    // -> 61.0
\bx3.midi    // -> 61.0

SPN.midi(69.5)        // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ] 
69.5.midispn          // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
SPN.midi(69.5, \lily) // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
69.5.midispn(\lily)   // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
SPN.midi(69.5, \mus)  // -> [ a ¼↑ 4, b♭ ¼↓ 4, a♯ ¼↓ 4, g𝄪 ¼↑ 4, b𝄫 ¼↑ 4, c𝄫 ¼↓5 ]
69.5.midispn(\mus)    // -> [ a ¼↑ 4, b♭ ¼↓ 4, a♯ ¼↓ 4, g𝄪 ¼↑ 4, b𝄫 ¼↑ 4, c𝄫 ¼↓5 ]
SPN.midi(69.5, \ez)   // -> [ aq4, bU4, gSq4, bFq4, cFu5 ]
69.5.midispn(\ez)     // -> [ aq4, bU4, gSq4, bFq4, cFu5 ]

69.5.midicps                    // -> 452.89298412314
SPN.cps(452.89298412314)        // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
452.8929841231.cpsspn           // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
SPN.cps(452.89298412314, \lily) // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
452.8929841231.cpsspn(\lily)    // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
SPN.cps(452.89298412314, \mus)  // -> [ a ¼↑ 4, b♭ ¼↓ 4, a♯ ¼↓ 4, g𝄪 ¼↑ 4, b𝄫 ¼↑ 4, c𝄫 ¼↓5 ]
452.8929841231.cpsspn(\mus)     // -> [ a ¼↑ 4, b♭ ¼↓ 4, a♯ ¼↓ 4, g𝄪 ¼↑ 4, b𝄫 ¼↑ 4, c𝄫 ¼↓5 ]
SPN.cps(452.89298412314, \ez)   // -> [ aq4, bU4, gSq4, bFq4, cFu5 ]
452.89298412314.cpsspn(\ez)     // -> [ aq4, bU4, gSq4, bFq4, cFu5 ]

A new class with some methods and new methods for some classes:

SPN : Notator {
	*prEzForOctave {
		^PitchClass.prEzOctaveLowerPart
		++ PitchClass.prEzCommonPart
		++ PitchClass.prEzOctaveHigherPart
	}
	*prLilyForOctave {
		^PitchClass.prLilyOctaveLowerPart
		++ PitchClass.prLilyCommonPart
		++ PitchClass.prLilyOctaveHigherPart
	}
	*prMusForOctave {
		^PitchClass.prMusicSymbolOctaveLowerPart
		++ PitchClass.prMusicSymbolCommonPart
		++ PitchClass.prMusicSymbolOctaveHigherPart
	}
	*prParseOctave {
		arg aPitchClassAsSymbol, octave;
		var aPitchClassAsString, result;
		aPitchClassAsString = aPitchClassAsSymbol.asString;
		result = if (aPitchClassAsString[aPitchClassAsString.size - 1] == $h) {
			aPitchClassAsString[.. aPitchClassAsString.size - 2] ++ (octave.asInteger + 1)
		} {
			if (aPitchClassAsString[aPitchClassAsString.size - 1] == $l) {
				aPitchClassAsString[.. aPitchClassAsString.size - 2] ++ (octave.asInteger - 1)
			}
			{
				aPitchClassAsSymbol ++ octave.asInteger
			}
		};
		^result
	}
	*prMapToOctave {
		arg enharmonicQuarterToneDictionaryOfPitchClasses, midiPitch;
		var octave = (midiPitch / 12).floor - 1;
		^enharmonicQuarterToneDictionaryOfPitchClasses.collect { |aPitchClassAsSymbol|
			SPN.prParseOctave(aPitchClassAsSymbol, octave)
		}
	}
	*prDetectBInLowerOctave {
		arg pitchClass, octave;
		octave = if ("bs|bQ|bSu|bS|bSq|bs|btqs|bxqf|bx|bxqs|b♯|b♯ ¼↑|b𝄪 ¼↓|b𝄪|b𝄪 ¼↑".matchRegexp(pitchClass.asString)) {
			octave + 1
		} {
			octave
		};
		^octave
	}
	*prDetectPitchClassOctave {
		arg pitchName;
		var pitchNameAsString, size, pitchClass, octave;
		pitchNameAsString = pitchName.asString;
		size = pitchNameAsString.size;
		# pitchClass, octave = if (pitchNameAsString[size - 2] == $- || (pitchNameAsString[size - 2] == $m)) {

			[pitchNameAsString[0 .. size - 3].asSymbol, pitchNameAsString[size -1].asString.asInteger * -1]
		} {
			[pitchNameAsString[0 .. size - 2].asSymbol, pitchNameAsString[size -1].asString.asInteger]
		}
		^[pitchClass, SPN.prDetectBInLowerOctave(pitchClass, octave)]
	}
	*getValue {
		arg which, mappingSwitch;
		var pitchClass, octave, pitchClassSets, pitchClassKey, midiPitchNumber;
		pitchClassSets = PitchClass.lily.collect { |item, index|
			item ++ PitchClass.ez[index] ++ PitchClass.mus[index] };
		# pitchClass, octave = SPN.prDetectPitchClassOctave(which);
		pitchClassSets.do { |pitchClassSet|
			if (pitchClassSet.includes(pitchClass)) {
				pitchClassKey = pitchClassSets.findKeyForValue(pitchClassSet).asString.replace("t", "10").replace("e", "11").interpret
			}
		};
		midiPitchNumber = (octave + 1) * 12 + pitchClassKey;
		^switch(mappingSwitch,
			\cps, { midiPitchNumber.midicps },
			\midi, { midiPitchNumber.asFloat }
		)
	}
	*prStyle { arg style;
		^switch(style,
			\ez, { SPN.prEzForOctave },
			\lily, { SPN.prLilyForOctave },
			\mus, { SPN.prMusForOctave }
		)
	}
	*midi {
		arg midiPitch, style = \lily;
		var enharmonicQuarterToneDictionaryOfPitchClasses = SPN.prStyle(style)[midiPitch.round(0.5) * 2 % 24];
		^SPN.prMapToOctave(enharmonicQuarterToneDictionaryOfPitchClasses, midiPitch)
	}
	*cps {
		arg freq, style = \lily;
		var enharmonicQuarterToneDictionaryOfPitchClasses = SPN.prStyle(style)[freq.cpsmidi.round(0.5) * 2 % 24];
		^SPN.prMapToOctave(enharmonicQuarterToneDictionaryOfPitchClasses, freq.cpsmidi)
	}
}

+ PitchClass {
	*prMake {
		arg quarterToneScale;
		var result;
		result = quarterToneScale.collectAs({ |pitchClass, index|
			var key, keyAsString, keySizeAsString;
			key = index / 2;
			key-> pitchClass }, IdentityDictionary);

		// adding integer
		(0..11).do { |i|
			result.add(i -> result[i.asFloat]);
		};

		// adding \t, \t5, 't.5', \e, \e5 and 'e.5'
		result.add(\t -> result[10]);
		result.add('t.5' -> result[10.5]);
		result.add(\t5 -> result[10.5]);
		result.add(\e -> result[11]);
		result.add('e.5' -> result[11.5]);
		result.add(\e5 -> result[11.5]);
		^result // 24 + 12 + 6 = 42
	}
	*prEzCommonPart {
		^[
			['dn',		'd',											'cS',		'eF'	],
			['dq',					'eU',					'fFu',		'cSq',		'eFq'	],
			['ds',		'ef',								'fF'							],
			['eu',					'dQ',		'fU',		'fFq'							],
			['en',		'e',		'ff',								'dS'				],
			['eq',		'fu',								'dSq',		'gFu'				],
			['fn',		'f',		'es',								'gF'				],
			['fq',		'eQ',					'gU',		'gFq',		'eSu'				],
			['fs',		'gf',								'eS'							],
			['gu',		'fQ',								'eSq',		'fSu',		'aFu'	],
			['gn',		'g',											'fS',		'aF'	],
			['gq',		'aU',								'fSq',		'aFq'				],
			['gs',		'af'																],
			['au',		'gQ',								'gSu',		'bFu'				],
			['an',		'a',											'gS',		'bF'	]
		]
	}
	*prEzClassLowerPart {
		^[
			['cn',		'c',		'bs',								'dF'				],
			['cq',					'bQ',		'dU',		'dFq',		'bSu'				],
			['cs',		'df',								'bS'							],
			['du',					'cQ',					'bSq',		'cSu',		'eFu'	]
		]
	}
	*prEzClassHigherPart {
		^[
			['aq',		'bU',								'gSq',		'bFq',		'cFu'	],
			['as',		'bf',								'cF'							],
			['bu',		'aQ',					'cU',		'cFq'							],
			['bn',		'b',											'aS',		'cf'	],
			['bq',		'cu',								'aSq',		'dFu'				]
		]
	}
	*prEzOctaveLowerPart	{
		^[
			['cn',		'c',		'bsl',								'dF'				],
			['cq',					'bQl',		'dU',		'dFq',		'bSul'				],
			['cs',		'df',								'bSl'							],
			['du',					'cQ',					'bSql',		'cSu',		'eFu'	]
		]
	}
	*prEzOctaveHigherPart{
		^[
			['aq',		'bU',								'gSq',		'bFq',		'cFuh'	],
			['as',		'bf',								'cFh'							],
			['bu',		'aQ',					'cUh',		'cFqh'							],
			['bn',		'b',											'aS',		'cfh'	],
			['bq',		'cuh',								'aSq',		'dFuh'				]
		]
	}
	*ez {
		var quarterToneScale;
		quarterToneScale = PitchClass.prEzClassLowerPart
		++ PitchClass.prEzCommonPart
		++ PitchClass.prEzClassHigherPart;
		^PitchClass.prMake(quarterToneScale)
	}
	*prLilyCommonPart {
		^[
			['d',											'cx',		'eff'				],
			['dqs',					'etqf',					'fffqf',	'cxqs',		'effqs'	],
			['ds',		'ef',								'fff'							],
			['eqf',					'dtqs',		'ftqf',		'fffqs'							],
			['e',		'ff',								'dx'							],
			['eqs',		'fqf',								'dxqs',		'gffqf'				],
			['f',		'es',								'gff'							],
			['fqs',		'etqs',					'gtqf',		'gffqs',	'exqf'				],
			['fs',		'gf',								'ex'							],
			['gqf',		'ftqs',								'exqs',		'fxqf',		'affqf'	],
			['g',											'fx',		'aff'				],
			['gqs',		'atqf',								'fxqs',		'affqs'				],
			['gs',		'af'																],
			['aqf',		'gtqs',								'gxqf',		'bffqf'				],
			['a',											'gx',		'bff'				]
		]
	}
	*prLilyClassLowerPart {
		^[
			['c',		'bs',								'dff'							],
			['cqs',					'btqs',		'dtqf',		'dffqs',	'bxqf'				],
			['cs',		'df',								'bx'							],
			['dqf',					'ctqs',					'bxqs',		'cxqf',		'effqf'	]
		]
	}
	*prLilyClassHigherPart {
		^[
			['aqs',		'btqf',								'gxqs',		'bffqs',	'cffqf'	],
			['as',		'bf',								'cff'							],
			['bqf',		'atqs',					'ctqf',		'cffqs'							],
			['b',											'ax',		'cf'				],
			['bqs',		'cqf',								'axqs',		'dffqf'				]
		]
	}
	*prLilyOctaveLowerPart {
		^[
			['c',		'bsl',								'dff'							],
			['cqs',					'btqsl',	'dtqf',		'dffqs',		'bxqfl'			],
			['cs',		'df',								'bxl'							],
			['dqf',					'ctqs',					'bxqsl',	'cxqf',		'effqf'	]
		]
	}
	*prLilyOctaveHigherPart {
		^[
			['aqs',		'btqf',								'gxqs',		'bffqs',	'cffqfh'],
			['as',		'bf',								'cffh'							],
			['bqf',		'atqs',					'ctqfh',	'cffqsh'						],
			['b',											'ax',		'cfh'				],
			['bqs',		'cqfh',								'axqs',		'dffqfh'			]
		]
	}
	*lily {
		var quarterToneScale;
		quarterToneScale = PitchClass.prLilyClassLowerPart
		++ PitchClass.prLilyCommonPart
		++ PitchClass.prLilyClassHigherPart;
		^PitchClass.prMake(quarterToneScale)
	}
	*prMusicSymbolCommonPart {
		^[
			['d♮',								'c𝄪',		'e𝄫'							],
			['d ¼↑ ',	'd♯ ¼↓ ',	'e♭ ¼↓ ',				'f𝄫 ¼↓ ',	'c𝄪 ¼↑ ',		'e𝄫 ¼↑ '	],
			['d♯',		'e♭',								'f𝄫'							],
			['e♮ ¼↓ ',	'e♭ ¼↑ ',	'd♯ ¼↑ ',	'f♭ ¼↓ '	,	'f𝄫 ¼↑ '							],
			['e♮',		'f♭',								'd𝄪'								],
			['e ¼↑ ',	'f ¼↓ ', 	'f♭ ¼↑ ',	'e♯ ¼↓ ',	'd𝄪 ¼↑ ',		'g𝄫 ¼↓ '				],
			['f♮',		'e♯',								'g𝄫'							],
			['f ¼↑ ',	'e♯ ¼↑ ',	'f♯ ¼↓ ',	'g♭ ¼↓ ',	'g𝄫 ¼↑ ',	'e𝄪 ¼↓ '				],
			['f♯',		'g♭',								'e𝄪'								],
			['g ¼↓ ',	'f♯ ¼↑ ',	'g♭ ¼↑ ',	'e𝄪 ¼↑ ',		'f𝄪 ¼↓ ',		'a𝄫 ¼↓ '				],
			['g♮',											'f𝄪',		'a𝄫'				],
			['g ¼↑ ',	'a♭ ¼↓ ',	'g♯ ¼↓ ',							'f𝄪 ¼↑ ',		'a𝄫 ¼↑ '	],
			['g♯',		'a♭'																],
			['a ¼↓ ',	'g♯ ¼↑ ',	'a♭ ¼↑ ',				'g𝄪 ¼↓ ',		'b𝄫 ¼↓ '				],
			['a♮',											'g𝄪',		'b𝄫'				]
		]
	}
	*prMusicSymbolClassLowerPart {
		^[
			['c♮',		'b♯',								'd𝄫'							],
			['c ¼↑ ',	'c♯ ¼↓ ',	'b♯ ¼↑ ',	'd♭ ¼↓ ',	'd𝄫 ¼↑ ',	'b𝄪 ¼↓ '				],
			['c♯',		'd♭',								'b𝄪'								],
			['d ¼↓ ',	'd♭ ¼↑ ',	'c♯ ¼↑ ',				'b𝄪 ¼↑ ',		'c𝄪 ¼↓ ',		'e𝄫 ¼↓ '	]
		]
	}
	*prMusicSymbolClassHigherPart {
		^[
			['a ¼↑ ',	'b♭ ¼↓ ',	'a♯ ¼↓ ',	'g𝄪 ¼↑ ',		'b𝄫 ¼↑ ',	'c𝄫 ¼↓ '				],
			['a♯',		'b♭',								'c𝄫'							],
			['b ¼↓ ',	'a♯ ¼↑ ',	'b♭ ¼↑ ',	'c♭ ¼↓ ',	'c𝄫 ¼↑ '							],
			['b♮',											'a𝄪',		'c♭'				],
			['b ¼↑ ',	'c ¼↓ ',		'c♭ ¼↑ ',	'b♯ ¼↓ ',	'a𝄪 ¼↑ ',		'd𝄫 ¼↓ '				]
		]
	}
	*prMusicSymbolOctaveLowerPart {
		^[
			['c♮',		'b♯l',								'd𝄫'							],
			['c ¼↑ ',	'c♯ ¼↓ ',	'b♯ ¼↑l',	'd♭ ¼↓ ',	'd𝄫 ¼↑ ',	'b𝄪 ¼↓l'				],
			['c♯',		'd♭',								'b𝄪l'							],
			['d ¼↓ ',	'd♭ ¼↑ ',	'c♯ ¼↑ ',				'b𝄪 ¼↑l',	'c𝄪 ¼↓ ',		'e𝄫 ¼↓ '	]
		]
	}
	*prMusicSymbolOctaveHigherPart {
		^[
			['a ¼↑ ',	'b♭ ¼↓ ',	'a♯ ¼↓ ',	'g𝄪 ¼↑ ',		'b𝄫 ¼↑ ',	'c𝄫 ¼↓h'			],
			['a♯',		'b♭',								'c𝄫h'							],
			['b ¼↓ ',	'a♯ ¼↑ ',	'b♭ ¼↑ ',	'c♭ ¼↓h',	'c𝄫 ¼↑h'						],
			['b♮',											'a𝄪',		'c♭h'				],
			['b ¼↑ ',	'c ¼↓h',	'c♭ ¼↑h',	'b♯ ¼↓ ',	'a𝄪 ¼↑ ',		'd𝄫 ¼↓h'			]
		]
	}
	*mus {
		var quarterToneScale;
		quarterToneScale = PitchClass.prMusicSymbolClassLowerPart
		++ PitchClass.prMusicSymbolCommonPart
		++ PitchClass.prMusicSymbolClassHigherPart;
		^PitchClass.prMake(quarterToneScale)
	}
	*getName {
		arg which;
		var pitchClassSets, pitchClassKey;
		pitchClassSets = PitchClass.lily.collect { |item, index|
			item ++ PitchClass.mus[index] ++ PitchClass.ez[index] };
		^pitchClassSets[which]
	}

}

+ Float {
	cpsspn {
		arg style = \lily;
		^SPN.midi(this.cpsmidi.round(0.5), style)
	}
	midispn {
		arg style = \lily;
		^SPN.midi(this.round(0.5), style)
	}
	pitchClass {
		^PitchClass.getName(this)
	}
}

+ Integer {
	cpsspn {
		arg style = \lily;
		^SPN.midi(this.cpsmidi.round(0.5), style)
	}
	midispn {
		arg style = \lily;
		^SPN.midi(this.round(0.5), style)
	}
	pitchClass {
		^PitchClass.getName(this.asFloat)
	}
}

+ Symbol {
	cps {
		^SPN.getValue(this, \cps)
	}
	midi {
		^SPN.getValue(this, \midi)
	}
}

Here are more test codes with results in the Post window:

\d4.cps                 // -> 293.66476791741
\d4.midi                // -> 62.0
\dn4.cps                // -> 293.66476791741
\dn4.midi               // -> 62.0

\df4.cps                // -> 277.18263097687
\df4.midi               // -> 61.0
61.midispn              // -> [ cs4, df4, bx3 ]
61.midispn(\lily)       // -> [ cs4, df4, bx3 ]
61.midispn(\mus)        // -> [ c♯4, d♭4, b𝄪3 ]
277.18263097687.cpsspn  // -> [ cs4, df4, bx3 ]
277.cpsspn(\lily)       // -> [ cs4, df4, bx3 ]
278.cpsspn(\mus)        // -> [ c♯4, d♭4, b𝄪3 ]

PitchClass.lily[\t]     // -> [ as, bf, cff ]
PitchClass.lily['t.5']  // -> [ bqf, atqs, ctqf, cffqs ]
PitchClass.lily[\t5]    // -> [ bqf, atqs, ctqf, cffqs ]
PitchClass.lily[\e]     // -> [ b, ax, cf ]
PitchClass.lily[\e5]    // -> [ bqs, cqf, axqs, dffqf ]
PitchClass.lily['e.5']  // -> [ bqs, cqf, axqs, dffqf ]

SPN.midi(69)    // -> [ a4, gx4, bff4 ]
SPN.midi(69.2)  // -> [ a4, gx4, bff4 ]
SPN.midi(69.3)  // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
SPN.midi(69.5)  // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
SPN.midi(69.6)  // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
SPN.midi(69.9)  // -> [ as4, bf4, cff5 ]
SPN.midi(70.0)  // -> [ as4, bf4, cff5 ]

SPN.midi(69, \mus)    // -> [ a♮4, g𝄪4, b𝄫4 ]
SPN.midi(69.1, \mus)  // -> [ a♮4, g𝄪4, b𝄫4 ]
SPN.midi(69.4, \mus)  // -> [ a ¼↑ 4, b♭ ¼↓ 4, a♯ ¼↓ 4, g𝄪 ¼↑ 4, b𝄫 ¼↑ 4, c𝄫 ¼↓5 ]
SPN.midi(69.5, \mus)  // -> [ a ¼↑ 4, b♭ ¼↓ 4, a♯ ¼↓ 4, g𝄪 ¼↑ 4, b𝄫 ¼↑ 4, c𝄫 ¼↓5 ]
SPN.midi(69.6, \mus)  // -> [ a ¼↑ 4, b♭ ¼↓ 4, a♯ ¼↓ 4, g𝄪 ¼↑ 4, b𝄫 ¼↑ 4, c𝄫 ¼↓5 ]
SPN.midi(69.9, \mus)  // -> [ a♯4, b♭4, c𝄫5 ]
SPN.midi(70.0, \mus)  // -> [ a♯4, b♭4, c𝄫5 ]

SPN.midi(72)          // -> [ c5, bs4, dff5 ]
SPN.midi(72, \mus)    // -> [ c♮5, b♯4, d𝄫5 ]

SPN.cps(110)          // -> [ a2, gx2, bff2 ]
SPN.cps(111.0, \mus)  // -> [ a♮2, g𝄪2, b𝄫2 ]
SPN.cps(112.0)        // -> [ aqs2, btqf2, gxqs2, bffqs2, cffqf3 ]
SPN.cps(113, \mus)    // [ a ¼↑ 2, b♭ ¼↓ 2, a♯ ¼↓ 2, g𝄪 ¼↑ 2, b𝄫 ¼↑ 2, c𝄫 ¼↓3 ]
SPN.cps(114.0)        // -> [ aqs2, btqf2, gxqs2, bffqs2, cffqf3 ]
SPN.cps(115, \mus)    // -> [ a♯2, b♭2, c𝄫3 ]
SPN.cps(116.0)        // -> [ as2, bf2, cff3 ]
SPN.cps(117.0, \mus)  // -> [ a♯2, b♭2, c𝄫3 ]
SPN.cps(118)          // -> [ as2, bf2, cff3 ]
SPN.cps(119, \mus)    // -> [ b ¼↓ 2, a♯ ¼↑ 2, b♭ ¼↑ 2, c♭ ¼↓3, c𝄫 ¼↑3 ]
SPN.cps(120)          // -> [ bqf2, atqs2, ctqf3, cffqs3 ]


\a2.cps               // -> 110.0
110.0.cpsspn          // -> [ a2, gx2, bff2 ]
111.0.cpsspn(\mus)    // -> [ a♮2, g𝄪2, b𝄫2 ]
110.cpsspn            // -> [ a2, gx2, bff2 ]
111.cpsspn(\mus)      // -> [ a♮2, g𝄪2, b𝄫2 ]

\aq2.cps              // -> 113.22324603078
112.cpsspn            // -> [ aqs2, btqf2, gxqs2, bffqs2, cffqf3 ]
113.0.cpsspn          // -> [ aqs2, btqf2, gxqs2, bffqs2, cffqf3 ]
114.cpsspn            // -> [ aqs2, btqf2, gxqs2, bffqs2, cffqf3 ]

\as2.cps              // -> 116.54094037952
115.cpsspn            // -> [ as2, bf2, cff3 ]
116.cpsspn            // -> [ as2, bf2, cff3 ]
117.cpsspn            // -> [ as2, bf2, cff3 ]
118.cpsspn            // -> [ as2, bf2, cff3 ]

\aQ2.cps              // -> 119.95585059318
119.cpsspn            // -> [ bqf2, atqs2, ctqf3, cffqs3 ]
120.cpsspn            // -> [ bqf2, atqs2, ctqf3, cffqs3 ] 
121.cpsspn            // -> [ bqf2, atqs2, ctqf3, cffqs3 ]

\b2.cps          // -> 123.47082531403
122.cpsspn       // -> [ b2, ax2, cf3 ]
122.cpsspn(\mus) // -> [ b♮2, a𝄪2, c♭3 ]

\a4.cps
\a4.midi
69.midispn           // -> [ a4, gx4, bff4 ]
69.0.midispn(\lily)  // -> [ a4, gx4, bff4 ]
69.1.midispn(\mus)   // -> [ a♮4, g𝄪4, b𝄫4 ]
69.2.midispn(\mus)   // -> [ a♮4, g𝄪4, b𝄫4 ]
69.3.midispn(\mus)   // -> [ a ¼↑ 4, b♭ ¼↓ 4, a♯ ¼↓ 4, g𝄪 ¼↑ 4, b𝄫 ¼↑ 4, c𝄫 ¼↓5 ]
69.4.midispn         // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
69.5.midispn         // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
69.6.midispn         // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
69.7.midispn         // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
69.8.midispn         // -> [ as4, bf4, cff5 ]
69.9.midispn         // -> [ as4, bf4, cff5 ]
70.midispn           // -> [ as4, bf4, cff5 ]
70.0.midispn         // -> [ as4, bf4, cff5 ]
70.1.midispn         // -> [ as4, bf4, cff5 ]
70.2.midispn         // -> [ as4, bf4, cff5 ]
70.3.midispn         // -> [ bqf4, atqs4, ctqf5, cffqs5 ]
70.4.midispn         // -> [ bqf4, atqs4, ctqf5, cffqs5 ]
70.5.midispn         // -> [ bqf4, atqs4, ctqf5, cffqs5 ]
70.6.midispn         // -> [ bqf4, atqs4, ctqf5, cffqs5 ]
70.7.midispn         // -> [ bqf4, atqs4, ctqf5, cffqs5 ]
70.8.midispn         // -> [ b4, ax4, cf5 ]
70.9.midispn         // -> [ b4, ax4, cf5 ]
71.midispn           // -> [ b4, ax4, cf5 ]
1 Like

What do you think about the following hierarchy and structure of notes and rests?
I think it would be better to remove SPN and PitchClass from the notation scheme I am proposing…

Notation: a subclass of the class Object, and an abstract parent class for ‘Notation Library’.
Attribute: an abstract parent class for its subclasses
╎ ├ Staff
╎ ├ Clef
╎ ├ Tempo
╎ ├ TimeSignature
╎ └ KeySignature
Entry: an abstract parent class for its subclasses
╎ ├ Intensity: a class that creates an instance of an Intensity class
╎ ├ I: the abbreviation of Intensity
╎ ├ Hairpin: a class that creates an instance of a hairpin
╎ ├ H: the abbreviation of Hairpin
╎ ├ Rest: a class that creates an instance of a rest
╎ ├ R: the abbreviation of Rest
╎ ├ Note: an abstract class for each pitch class name and the attachment to the note instance:
╎ ╎├ A: a class that creates an instance of note utilising the pitch class a
╎ ╎├ B: a class that creates an instance of note utilising the pitch class b
╎ ╎├ C: a class that creates an instance of note utilising the pitch class c
╎ ╎├ D: a class that creates an instance of note utilising the pitch class d
╎ ╎├ E: a class that creates an instance of note utilising the pitch class e
╎ ╎├ F: a class that creates an instance of note utilising the pitch class f
╎ ╎├ G: a class that creates an instance of note utilising the pitch class g
╎ ╎├ Articulation: a class that creates an instance of articulation for an instance of note
╎ ╎├ U: the abbreviation of Articulation
╎ ╎├ Tie: a class that creates an instance of tie for an instance of note
╎ ╎├ T: the abbreviation of Tie
╎ ╎├ Slur: a class that creates an instance of slur for an instance of note
╎ ╎└ S: the abbreviation of Slur
etc.

An instance of Rest can be created as follows:

  • R.4: rest 1/4 ← It is currently not possible in sclang.
  • R.c: rest 1/4 (crochet rest)
  • R.q: rest 1/4 (quarter rest)
  • R.4d: rest dotted 1/4 ← It is currently not possible in sclang.
  • R.cd: rest dotted 1/4 (dotted crochet rest)
  • R.qd: rest dotted 1/4 (dotted quarter rest)
  • R.8: rest 1/8 ← It is currently not possible in sclang.
  • R.v: rest 1/8 (quaver rest)
  • R.e: rest 1/8 (eighth rest)
  • R.8d: rest dotted 1/8 ← It is currently not possible in sclang.
  • R.vd: rest dotted 1/8 (dotted crochet rest)
  • R.ed: rest dotted 1/8 (dotted quarter rest)
  • etc.

An instance of Note can be created as follows:
(All methods starting with integer are currently not possible in sclang.)

  • A.n.4.4: a quarter note of the A natural in octave 4. MIDI pitch number 69.

  • g.x.4.c: a quarter note of the G double sharps in octave 4. MIDI pitch number 69.

  • B.ff.4.q: a quarter note of the B double flats in octave 4. MIDI pitch number 69.

  • A.qs.4.4d: a dotted quarter note of a quarter-tone higher than A in octave 4. MIDI pitch number 69.5.

  • B.tqf.4.cd: a dotted quarter note of three quarter-tone lower than B in octave 4. MIDI pitch number 69.5.

  • A.s.4.4dd: a double-dotted quarter note of the A sharp in octave 4. MIDI pitch number 70.

  • B.f.4.cdd: a double-dotted quarter note of the B flat in octave 4. MIDI pitch number 70.

  • C.ff.5.qdd: a double-dotted quarter note of the C double flat in octave 5. MIDI pitch number 70.

  • etc.

This will change the notation of a musical notation in my another post: Musical Notation in SC using musicXML - #17 by prko

  • an entry with full options in my initial idea:
[68, \e, \s2, \S, \r, \u] // midinote: 68, duration: 1/8, dynamic marking: strong2 i.e. ff, articulation: staccatissimo, tie: laissez vibrer, slur: start
  • the same entry changed version:
[A.f.4.e, Intensity.ff, Articulation.x, Tie.r, Slur.s] // A pitch and duration are combined to form a note.

or

[A.f.4.e, I.ff, U.x, T.r, S.s] // A pitch and duration are combined to form a note.

Which example is practical for reading and writing?

1 Like

Your new system allows you to do things like

[A, C, E,].collect( _.4.e)
or [A, C, E].collect{|i| i.(4.rrand(6))}

Which is interesting.

I find this:

a lttlle hard to read though - my eye goes to the capital letters before the salient part which here is the method. But maybe that’s ok.

If I were building this I would keep my eye on Lilypond export - so for example in Lilypond you can send a dynamic marking above the staff by adding ^ so \^ff - a nice thing about having Intensity objects is that you can add properties like \above and \below to them later as opposed to your initial idea.

I would perhaps use the more conventional term Dynamic though perhaps?

2 Likes

what do you mean ‘removing PitchClass’? It’s part of a quark that uses it for some very specific things (including transposition, inversion, etc). and really is kind of different than the rest of this.

2 Likes

I think OP is talking about his extensions to PitchClass above - he used PitchClass inside his notation scheme but now thinking not to

1 Like

@semiquaver
Thank you for your opinions. The code examples are impressive! Also, \^ff is very handy!

@jordan
I have added a LilyPond style pitch class to SPN. But isn’t it strange to express SPN with a LilyPond style pitch class name because of its octave number?

SPN.midi(69, \lily) // -> [ a4, gx4, bff4 ]
SPN.midi(69, \mus)  // -> [ a♮4, g𝄪4, b𝄫4 ]
SPN.midi(69, \ez)   // -> [ an4, a4, gS4, bF4 ]

@josh
As @semiquaver wrote, this means removing PitchClass from my notation scheme. I apologise for writing the wrong sentence.
I inspected your PitchClass quark, and I think that you and I have a different view of PitchClass… At the moment, I am not sure that it is a good idea to integrate my method into your PitchClass. The following is what I found in your PitchClass class and methods:

// PitchClass in ctk Quark
b = PitchClass.new(\a, octave: 4, alter: 0) // quarter-tone is not implemented
b.lilyString          // a'
b.guidoString         // a1
b.note                // -> a 
b.octave              // -> 4
b.freq                // 440
b.pitchclass          // -> 9 // this is the integer notation. I think this method should also return the pitch class name a. For example: [a, 9]. If getting the integer is required, I think the method should be .integer or .integerName.
b.pitch               // -> an // This is also a pitch class because it does not contain any octave information. Why does it not return a pitch corresponding to b.freq? 
b.midinote            // It would be nice if this method existed to get the corresponding MIDI pitch number.
b.midi                // Could be an abbreviation of .midinote.

// in comparison to mine 
PitchClass.getName(0)  // -> [ c, bs, dff, c♮, b♯, d𝄫, cn, c, bs, dF ]
PitchClass.getName(9)  // -> [ a, gx, bff, a♮, g𝄪, b𝄫, an, a, gS, bF ]
PitchClass.lily[\t]    // -> [ as, bf, cff ]
PitchClass.lily['t.5'] // -> [ bqf, atqs, ctqf, cffqs ]
PitchClass.lily[\t5]   // -> [ bqf, atqs, ctqf, cffqs ]
\c4.pitchClass         // -> 0.0
\cn4.pitchClass        // -> 0.0
\cqs4.pitchClass       // -> 0.5
\cs4.pitchClass        // -> 1.0
9.pitchClass           // -> [ a, gx, bff, a♮, g𝄪, b𝄫, an, a, gS, bF ]
9.5.pitchClass         // -> [ aqs, btqf, gxqs, bffqs, cffqf, a ¼↑ , b♭ ¼↓ , a♯ ¼↓ , g𝄪 ¼↑ , b𝄫 ¼↑ , c𝄫 ¼↓ , aq, bU, gSq, bFq, cFu ]

// Ah! My .pitchClass method also seems to have problems with .integerNotation and .pitchClassName.

In my recent code, I am trying to use one type for accidents and another for note names. My rationale behind this is to provide myself with the ability to broaden or narrow down the scope of accidents (and what ET by selecting the accidentals) I wish to explore. This approach appears to offer a higher degree of flexibility. Why join note names and accidentals in the same type?

Another question, how do you calculate enharmonic pitches with your system?

How do you compare two pitches and get an interval? How can you use and manipulate intervals independently of pitches?

In my experiments, I also added a Maybe Arrow, so the pitch, whatever Accident, can have an inflection up or down.

Examples:

ghci > sharp ^. semitone
1 % 1

ghci > sharp ^. name
Sharp

ghci > sharp ^. abbreviation
"s"

ghci > flat & semitone .~ (1 % 1)
Accidental {_accName = Sharp, _accAbbreviation = "s", _accArrow = Nothing, _accSemitones = 1 % 1}

ghci> doubleFlat & arrow ?~ Up
Accidental {_accName = DoubleFlat, _accAbbreviation = "ff", _accArrow = Just Up, _accSemitones = (-2) 

ghci > flat & semitone +~ (1 % 3)
Accidental {_accName = ThirdFlat, _accAbbreviation = "rf", _accArrow = Nothing, _accSemitones = (-2) % 3}

ghci > flat + flat & semitone +~ (1 % 3)
Accidental {_accName = FiveSixthsFlat, _accAbbreviation = "fxf", _accArrow = Nothing, _accSemitones = (-5) % 3}

2 Likes

@prko Thanks a lot for your ideas! It’s awesome to see people digging into this lesser-explored area in SC.

I believe one of the factors impeding this progress is the presence of numerous quirks within musical notation. Implementation tends to mirror the contributor’s personal composition style and techniques at the time, which may not fit other composer visions.

In the real world, what composers truly crave is a means to manipulate their material. It’s only after this manipulation that the material takes on its visual form, whether it’s in the shape of MusicXML, Lily, Guido, or another standard.

As an arbitrary example, recently I came across a fascinating paper that explores different techniques for manipulating and constructing music scores here

If the intention is to expand the standard library, a comprehensive evaluation of all these aspects becomes essential. What’s needed is a neutral system that facilitates seamless integration with other intermediary systems.

We have to be clear about the best approach for this.

Just my opinion.

2 Likes

These symbols are meant to be shortcuts or also used in music ‘operations’?

Just an example:

\a4 @+ 2
->  b4

with something like

+ Symbol {   
  @+ { arg interval; 
  etc..
}

I also noted that “60s” and “60f” are parsed as “sharp” and “flat” alterations to Integers in the language already. Where is that implemented?

1 Like

Its here in PyrLexer.cpp

    } else if (c == 'b' || c == 's') {
        d = input();
        if (d >= '0' && d <= '9')
            goto accidental1;
        if (d == c)
            goto accidental2;
        goto accidental3;
    accidental1:
        d = input();
        if (d >= '0' && d <= '9')
            goto accidental1;
        unput(d);
        yytext[yylen] = 0;
        r = processaccidental1(yytext);
        goto leave;
    accidental2:
        d = input();
        if (d == c)
            goto accidental2;
    accidental3:
        unput(d);
        yytext[yylen] = 0;
        r = processaccidental2(yytext);
        goto leave;
2 Likes

To reduce typing!

We should use one of the subclasses of the collection class to handle note name, i.e. step in muscXML, and its accidental, i.e. alter in sclang:

~thisPitchClass = [\c, \n] // step, alter
~thisPitchClass = (step: \c, alter: \n) 

In real use we also need octave:

~thisPitch = [\c, \n, 4] // step, alter, octave
~thisPitch = (step: \c, alter: \n, octave: 4) 

If we use this as a symbol, we can easily combine ‘step’, ‘alter’ and ‘octave’ and parse the symbol via asString into each of them:

~pitchCombiner = { |step, alter, octave| (step ++ alter ++ octave).asSymbol }

(
~pitchParser = { |aPitch|
	var string, size, step, alt, octave;
	string = aPitch.asString;
	size = aPitch.asString.size;
	step = string[0];
	alt = string[1 .. size - 2];
	alt = if (alt[0] == $n || (alt[0] == $s) || (alt[0] == $f) || (alt[0] == $q) || (alt[0] == $t)) {
		alt
	} {
		alt.asFloat
	};
	octave = string[size-1];
	(step: step, alt: alt, octave: octave)
}
)

~pitch1 = ~pitchCombiner.(\a, \qs, 4)
~parsedPitch1 = ~pitchParser.(~pitch1)
[~parsedPitch1[\step], ~parsedPitch1[\step].class]
[~parsedPitch1[\alt], ~parsedPitch1[\alt].class]
[~parsedPitch1[\octave], ~parsedPitch1[\octave].class]

~pitch2 = ~pitchCombiner.(\a, 0.5, 4)
~parsedPitch2 = ~pitchParser.(~pitch2)
[~parsedPitch2[\step], ~parsedPitch2[\step].class]
[~parsedPitch2[\alt], ~parsedPitch2[\alt].class]
[~parsedPitch2[\octave], ~parsedPitch2[\octave].class]

I’m not sure I understood your question correctly…
In my system, the output of a pitch or pitchclass query is an array. For example, midinote 60 or \c4 will return:

69.midispn // -> [ a4, gx4, bff4 ]
69.midispn(\mus) // -> [ a♮4, g𝄪4, b𝄫4 ]
440.cpsspn // -> [ a4, gx4, bff4 ]
440.cpsspn(\mus) // -> [ a♮4, g𝄪4, b𝄫4 ]

69.5.midispn // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
69.5.midispn(\mus) // -> -> [ a ¼↑ 4, b♭ ¼↓ 4, a♯ ¼↓ 4, g𝄪 ¼↑ 4, b𝄫 ¼↑ 4, c𝄫 ¼↓5 ]
452.89298412314.cpsspn // -> [ aqs4, btqf4, gxqs4, bffqs4, cffqf5 ]
452.89298412314.cpsspn(\mus) // -> -> [ a ¼↑ 4, b♭ ¼↓ 4, a♯ ¼↓ 4, g𝄪 ¼↑ 4, b𝄫 ¼↑ 4, c𝄫 ¼↓5 ]

0.pitchClass // -> [ c, bs, dff, c♮, b♯, d𝄫, cn, c, bs, dF ]

and each element in a pitch or a pitch class returns the same frequency or MIDI pitch number:

\a4.midi // -> 69.0
\a4.cps // -> 440.0
\gx4.midi // -> 69.0
\gx4.cps // -> 440.0
\bff4.midi // -> 69.0
\bff4.cps // -> 440.0

\a.pitchClass // -> 9.0  
\gss.pitchClass // -> 9.0
\bff.pitchClass // -> 9.0
\gx.pitchClass // error.... I should correct it.

So in my system there is no need to calculate enharmonic pitches when converting. However, there should be a calculation if your question is from a different perspective. Please let me know if my answer is based on a misunderstanding of your question.

It is not in my system, but sclang provides differentiate and -. So I could use them:

[\a4.midi, \a5.midi].differentiate[1..]

\a5.midi - \a4.midi

This only returns semitones. So I need to build a dictionary of intervals. Here I need to consider enharmonic intervals, so I should build the output as an array again.
I have not implemented the interval, but I could implement it.

Thank you for your question. Answering your question makes me very happy and has given me a wider perspective!

1 Like

Thanks for your opinion. I will read the article and add my thoughts and the sclang library feature!

1 Like

Yes, that makes sense. But I also see decisions as I mentioned in Euterpea, adding microtones to the framework is challenging because PitchClass directly corresponds to an integer, eventually linked to midi numbers. As simple as:

type Pitch = (PitchClass, Octave)
type Dur   = Rational
data PitchClass  =  Cff | Cf | C | Dff | Cs | Df | Css | D | Eff | Ds
                 |  Ef | Fff | Dss | E | Ff | Es | F | Gff | Ess | Fs
                 |  Gf | Fss | G | Aff | Gs | Af | Gss | A | Bff | As
                 |  Bf | Ass | B | Bs | Bss
     deriving (Show, Eq, Ord, Read, Enum, Bounded)

A flexible system that is able to accommodate any kind of accidental would look more like this:

data NoteName = C | D | E | F | G | A | B deriving (Eq, Show)

data PitchClass = PitchClass  
  { _noteName :: NoteName,
    _accidental :: Accidental
  }
  deriving (Eq, Show)

I brought up the concept of “enharmonic” pitches because it’s often necessary to change a pitch to its equivalent (flat and sharp as the simplest case), or to check if two pitches are enharmonically equivalent. This calls for dedicated operators for these situations.

cs =~ df
-- True

We haven’t yet discussed durations, but there are also unique aspects related to the relationship between durations in seconds and their representation in musical notation, which involves rational numbers and the concept of “dots,” “logic ties”, and if it belongs to one or more tuplet containers. It can be represented just as a rational number on a specific level, yes. But we need to always have all that information if we work with music notation (and export as MusicXML, for example)

The paper I mentioned deals with higher-level problems, once the music score needs to be manipulated. That is, real-world problems when we create scores, etc. For example, how can you modify multiple locations in a score (a tree) in a way that makes sense musically, etc?

1 Like

Agree that keeping accidentals as their own type is the most flexible - this doesn’t mean that you can’t use quick easy to type shortcuts though.

Regarding intervals there is a difference between augmented second (C to D#) and minor third (C to E-flat) so using midi numbers and integration is not adequate. Spelling is important in classical music to indicate harmonic function.

It is important that a note name class could be extended to represent different note name schemes like Slendro for example Slendro - Wikipedia.

Slendro has 5 note names and there is not necessarily a canonical mapping to frequency for them. (Of course this was also true for the Western system until recently!)

It would also be nice to be able to notate percussion music using names perhaps?

2 Likes

one note: sadly you cant send Integers as messages so A.s.4 wont work (nor can you start a message name with an integer so .3s won’t work either!

1 Like

@smoge

I think a variable could be used for a particular motif, a particular rhythmic pattern, a particular interval pattern, and so on. Then it could be used by operators or functions.

I ruled out parsing the enharmonic spelling of the MIDI pitch number because the enharmonic spelling is incompatible with MIDI. There should be an algorithm to parse the correct pitch with the correct accidental when pitches are entered as MIDI pitch numbers. Oh, I suppose it would be very tedious to develop such an algorithm. Is there any software that will display enharmonically correct pitches from a MIDI pitch number? I do not use MIDI files very often, so I do not know how it works. However, I sometimes open MIDI files with Logic Pro, Finale, MuseScore, Dorico, etc, and I remember having to correct some accidentals enharmonically.

Yes, I agree.

Anyway, there might not be such a big problem with enharmonic spelling when typing pitch with note names and accidentals.

  • When composing tonal music, you should use a basic triad dictionary or arrays.
  • When composing atonal music, the simplest pitch name is often better for readability. For example: a4 is better than gx4.
  • When using microtonal pitches, the simplest notation is always better for readability. For example: gqf4 is better than ``ftqfs4```.

Yes, I agree!

Yes, I know that, so I already added some notes in the post where I suggested these methods. I was thinking of asking for this possibility in SuperCollider 4, but I am still not sure if A.s.4 is optically good to use.

@smoge, @semiquaver
OK! I will divide the pitch notation into three components:

  • pitchclass (= step in musicXML)
  • accidental (= change in musicXML)
  • octave (= octave in musicXML)

Then,
\cqs4 should be [\c, \qs, \4]. Oh, how informative the Western notation system is!