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

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!

Here is the thing: \cqs4 can point to a Notename, an Accidental, and an Octave. You don’t need 3 symbols.

1 Like

The way I think of an accident type or class is that there is also the possibility of using a “CustomAccidental”. Nothing prevents that. The Notenames will always be the same, even if you choose a different tunning (not A=440hz).

Your example is a good one, and we can find more than one solution to work with that kind of music.

Nowadays we don’t have a problem with representing those symbols in MusicXML or LilyPond. With LilyPond, one can use the ekmelily extension. And Standard Music Font Layout / SMuFL is also quite good, the Bravura font is used by Dorico and other softwares.

1 Like

Maybe that makes a good case to use a small DSL using PreProcessor quark (or something equivalent) if we’re looking for readable and concise code.

1 Like

I haven’t used PreProcessor before but that sounds like a good idea -

I have done a little work with @shiihs scparco quark building parser trees and that might be a helpful tool as well.

3 Likes

Hey @prko

I think MusixXML 4.0 has some options beyond quarter-tones. Take a look:

https://www.w3.org/2021/06/musicxml40/musicxml-reference/data-types/accidental-value/

https://github.com/w3c/musicxml/issues/482

I think some work has been done in this area.

1 Like

Thanks for letting me know! I will try to implement some of them!

1 Like