Note Names / Pitch Classes

sorry about that - here you go - this one should work!

a = PtInterval(3, \major) + P(Interval(3, \major)
a.degree // 5
a.quality // \augmented
PtInterval {
	classvar rawIntervals = #[0, 1.5, 3.5, 5, 7, 8.5, 10.5, 12];
	classvar <perfectIntervalQualities, <imperfectIntervalQualities;
	var <degree, <quality;
	*initClass {
		perfectIntervalQualities = (diminished:-1,perfect:0,augmented:1);
		imperfectIntervalQualities = ('doubly-diminished': -2.5, diminished:-1.5,minor:-0.5,major:0.5,augmented:1.5,'doubly-augmented':2.5);
	}
	*new{ |degree quality|
		^super.newCopyArgs(degree, quality)
	}
	semitones {
		^( rawIntervals[ degree - 1 ] + 
			rawIntervals[ degree  - 1].isInteger.if{
				perfectIntervalQualities.at(quality)
			}{
				imperfectIntervalQualities.at(quality)
			}
		)
	}
	+ {|that|
		var outSemitones, outDegree, outQuality, rawInterval;
		outSemitones = this.semitones + that.semitones;
		outDegree = that.degree + degree - 1;
		rawInterval = rawIntervals[ outDegree - 1 ];
		outDegree.postln;
		rawInterval.postln;
		

		outQuality = rawInterval.isInteger.if{
				perfectIntervalQualities.findKeyForValue(( outSemitones - rawInterval ).asInteger )
		}{
				imperfectIntervalQualities.findKeyForValue(( outSemitones - rawInterval ).asInteger)
		};
		^PtInterval(outDegree,outQuality)
	}
}
1 Like

Cool!

The current algorithm appears correct after a couple of tests, but I find the algorithm quite mysterious to me. And I would have concerns about its scalability. For example, your reference is a list of floats, then rounded and used as indexes. This kind of thing could introduce complications or inaccuracies as the algorithm grows more complex in a larger project.

That’s my first impression just reading it.

And what if you start from F double flat? A system like music21 allows for triple flats and sharps.
(Although I can perfectly live with the absence of triple flats :slight_smile: ) More general maybe a pitch consists of 3 numbers: (degree, modifier, octave)?

1 Like

It works. (That’s just a minimal code I did without much checking). I think it will work as far as you add more intervals and qualities to the code.

The important thing is to have a solid algebra in an AffineSpace (vector space simplified without origin point). So making sure (with tests) that operations between pitches and intervals will be correct etc.

~c = [\C, \doubleFlat];
~perfectFourth = [\fourth, \perfect];
~resultingPitch = ~addInterval.(~c, ~perfectFourth);
//-> [ F, doubleFlat ]

Sorry I was not entirely clear: I meant adding a perfect fourth to an F double flat, which arrives in a B triple flat (or it can e.g. also return undefined, which is a choice that can be made).

1 Like

I’m not into this kind of tonal-like situations. It would be possible without much effort, I think. (I think you just need to add more intervals to the lookup table)

The enharmonic respelling based on flexible user-defined rules is something that makes more sense in my way of doing this kind of things (personally).

yes I should remedy that :wink:

This should not be a problem - the accidentals are kept separate from the notenames and should commute! so

Fbb + Pt(5, \diminished) would be calculated as F + Pt(5,\diminished) + bb (C-bbb)

I was thinking about why some intervals have a “perfect” form and others don’t - since seconds and thirds have to be major or minor the “default” second or third is between two semitones, hence the 0.5. Then all the qualities add or subtract some odd number of halves. The perfect intervals have a default that is on a semitone, so the qualities add or subtract even numbers of halves.

1 Like

Yes, absolutely. I just tried to show a simple working code here. the sc version I did just now, just to show the idea I was describing before

Here I add a separate PtAccidental class which has now a single method: asInterval. This method returns a unison interval - PtInterval(1, someQuality).

The idea is that F# + P4 is to be calculated by commuting the accidental

Accidental(\sharp).asInterval returns an augmented unison: PtInterval(1, \augmented)

So F# + P4 = F + (P4 + \sharp.asInterval) = F + PtInterval(4, \augmented) = B natural

You can test Accidental(\sharp).asInterval (or double-sharp) and add these results to PtInterval(3, \minor) etc -

TOFO I I have not implemented downward intervals P(-3, \major) or intervals greater than an octave - should be straightforward though… then I would have to actually implement the Pitches which will be something like PtPitch(noteName , PtAccidental, octave ) Just want to make sure the “algebra” is right

PtAccidental {
	var <accidental;  
	classvar unisonQualities;
	*initClass {
		unisonQualities = (
			'double-flat': 'doubly-diminished', 
			flat: 'diminished',
			natural: \perfect,
			sharp: \augmented,
			'double-sharp' :'doubly-augmented'
		);
	}

	*new { |accidental|
		^super.newCopyArgs(accidental)
	}

	asInterval {
		^PtInterval(1, unisonQualities.at(accidental))
	}
}
PtInterval {
	classvar rawIntervals = #[0, 1.5, 3.5, 5, 7, 8.5, 10.5, 12];
	classvar <perfectIntervalQualities, <imperfectIntervalQualities;
	var <degree, <quality;
	*initClass {
		perfectIntervalQualities = (diminished:-1,perfect:0,augmented:1);
		imperfectIntervalQualities = ('doubly-diminished': -2.5, diminished:-1.5,minor:-0.5,major:0.5,augmented:1.5,'doubly-augmented':2.5);
	}
	*new{ |degree quality|
		^super.newCopyArgs(degree, quality)
	}
	semitones {
		^( rawIntervals[ degree - 1 ] + 
			rawIntervals[ degree  - 1].isInteger.if{
				perfectIntervalQualities.at(quality)
			}{
				imperfectIntervalQualities.at(quality)
			}
		)
	}
	+ {|that|
		var outSemitones, outDegree, outQuality, rawInterval;
		outSemitones = this.semitones + that.semitones;
		outDegree = that.degree + degree - 1;
		rawInterval = rawIntervals[ outDegree - 1 ];
		outDegree.postln;
		rawInterval.postln;
		

		outQuality = rawInterval.isInteger.if{
				perfectIntervalQualities.findKeyForValue(( outSemitones - rawInterval ).asInteger )
		}{
				imperfectIntervalQualities.findKeyForValue(( outSemitones - rawInterval ).asInteger)
		};
		^PtInterval(outDegree,outQuality)
	}
}

I’ve not followed all of this, but the pitch model at event is actually quite nice too?

Schoenberg:

var d = [7b, 3, 4s, 2s, 4, 6, 2, 1s, 5, 6b, 7-7, 1] - 1;
Pbind('degree', d, 'dur', 1 / 2).play

Bach:

var o = 7; // nicer name for octave...
var d = [5, 3, 1, 6, 5, 1+o, 7s, 4, 3s, 2b+o, 1+o, 5, 4s, 3+o, 2+o, 1s+o, 2+o, 7, 5, 6s] - 1;
Pbind('scale', Scale.minor, 'degree', d, 'dur', 1 / 2).play

Note that the intervals are quite precise, i.e. the doubly diminished seventh, given by interval(3s, 2b+o), which can be written 7bb.

Ps. The one-indexing of common practice intervals means one wants the difference function to subtract one if negative else add one, i.e.

+SimpleNumber {

	interval { | aNumber |
		var x = aNumber - this;
		^(x >= 0).if {
			x + 1
		} {
			x - 1
		}
	}

}
2 Likes

I’ve noticed the b and s in the sc number parser, now I know what they do. They were made to work just with Pbind ‘degree’ ?

Yes, Sc is at the same time the “orchestra” and “score” language, so it’s nice to have some “affordances” for notation…

Ps. It should be straightforwards to extend the model to allow strings for degrees, so that something like the below would be accepted:

var d = "f d b g f b' a+ e d+ c-' b' f e+ d' c' b+' c' a f g+".split($ );
Pbind('scale', Scale.minor, 'root', 11, 'degree', d, 'dur', 1 / 2).play

Here + is raised one step, - is lowered one step, ’ is raised one octave, , is lowered one octave. (+ and - because in B-minor “c” is “c♯” and “c-” is “c♮”.) Also, the raise and lower qualifiers could read the amount from a field in the event, so if one liked + could be raise a half step (quarter tone), and ++ raise a step, &etc.

1 Like

I’m not a Jedi master about implementing Patterns in sc. I just studied a little to export streams to fomus (writing the superfomus quark many years ago).

But if that path is a good one, so be it. Let’s check it out.

@rdd

Have you ever played around with “quasiquotations”? I did a little test with a hyper simplified syntax inspired by lilypond. I wonder it it’s something worth exploring, or is it too “exotic” kind of thing to do?

>>> [lily|a'4 c'8 d'2|] 
[Note A (Octave 1) (1 % 4),Note C (Octave 1) (1 % 8),Note D (Octave 1) (1 % 2)]

@smoge I think the more usual. way to do this would be “a’4 c’8 d’2”.lily, no?

in either case the return value needs to be something - for you this is a Note object - I think we should have these available rather than relying on the degree field in Event, something lives between whatever DSL and both audio and notation. Notes would have methods to convert to Events, extract freq, to or render notation etc.

So you could write something like:

a = "e'4 b2".lily.asPbind(root: \e, instrument:\sawSynth)

and also

a = "e'4 b2".lily; Notate.musicXML( a, blah blah)

This way we can write any number of DSLs like .lily and still have a consistent interface that lives in a few related classes that can be subclassed straighforwardly

Re the degree field in Event it has going for it that it is already there!. But I have a hard time with the scheme of using the tenth digit to indicate sharps and flats!

1s // 1.1
2s // 1.2
2b // 1.9

So (degree:1.1) produces the same freq as (degree: 1.9) - and (degree: 1.5) the same freq as (degree: 0)

Not sure why this bugs me but it just seems wrong! (Its also annoying to extend)

Yes, but is there such parser out there? I think not.

no not now…

but my point is that if we have a system of objects for named notes that can be a target for any number of DSLs - right now many peoples efforts target either midinotes, or XML or are meant to be extracted into fields in Pbinds - if instead they produced arrays of NoteNames or Notes or whatever, and those Objects had methods to make Events or Pseqs or MusicXML, the same DSL could be used in patterns or notation or whatever without having to create methods for all of the above…

We could also operate on the Arrays of notes and build other data structures to represent music such as the prefix trees you proposed in the other thread!

I’ll try to creep ahead with my proof of concept to see if I can demonstrate!

I’ve been thinking about trying to use lilypond’s simplified syntax, but which is also valid code to manipulate with modifiers. It can be so many things, but it’s fun to think about it and explore.

In this case, a sequence of notes is repeated but the intervals are multiplied by different scalars. A bit like the diagrams DSL for graphics.

seq = = [lily| 3:2{c'4 d'8} 5:4{e'4 f'8 fis'8 ais'8} g'4 a'8 b'4 c''4 |]
mod =  hsep (seq ^. dur)   (map (\x -> seq # scaleIntervals x) [0.25, 0.8, 1, 1.5, 2.5])
1 Like

Have you ever played around with “quasiquotations”?

Indeed, macros are nice! OverloadedStrings is nice for simple things too…

1 Like

But I have a hard time with the scheme of using the tenth digit to indicate sharps and flats!

Yes, but the math works, and if one uses the flat and sharp suffixes one never see the fractional parts…

About degrees & alterations & equivalence, it’s all context sensitive, which is why degrees are such a nice model?

3b is the same “frequency” as 2s if there are two steps between 2 and 3 (c.f. diatonic major), but not if there is only one step (c.f. diatonic minor), where 3b is “the same as” 2, and 2s is “the same as” 3.

Also, for people who are finickity about such things, it can be nice to tune intervals slightly differently depending on context.

Anyways, one can map between all these representation as one likes, I just wanted to say something nice about the existing pitch model, which is quite flexible, and can be quite concise.

1 Like