Skip to content

Commit

Permalink
fix: pitchClassesToPianoChordNotes
Browse files Browse the repository at this point in the history
  • Loading branch information
Seanitzel committed Jul 17, 2023
1 parent e9f8ec0 commit 1fbf6e0
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 29 deletions.
48 changes: 32 additions & 16 deletions src/utilities/MusicFunctions.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,8 @@
import { PITCH_CLASS_LETTERS } from '../Constants.js';
import { Note, PitchClass, Scale } from '../types.js';
import { Note, PitchClass } from '../types.js';
import { rearrangeArray } from './GeneralFunctions.js';
import { enharmonicPitchClass, getPitchClassIndex, normalizePitchClass } from './PureMusicUtils.js';

/**
* Returns an array of notes with a specific octave.
* @param {Array} pitchClasses Array of pitch classes.
* @param {number} octave Octave to assign to notes..
* @returns {Array}
*/
export function pitchClassesToNotes(pitchClasses: Array<string>, octave: number): Array<string> {
return pitchClasses.map(pitchClass => `${pitchClass}${octave}`);
}

/**
* Returns an array of notes that represent a chord played on a piano in a certain octave.
* @param {Array} pitchClasses
Expand All @@ -25,22 +15,48 @@ export function pitchClassesToPianoChordNotes(pitchClasses: Array<PitchClass>, o
if(inversion) {
pitchClasses = rearrangeArray(pitchClasses, inversion) as Array<PitchClass>;
}

// const notes = pitchClassesToNotes(pitchClasses, octave);
// for(let i=1; i< notes.length; ++i) {
// const prevNote = notes[i-1];
// const normalizedPrevNote = normalizeNote(prevNote);
// const note = notes[i];
// const normalizedNote = normalizeNote(note);
// const { pitchClass, octave } = noteToObject(note);
// const { pitchClass: prevPitchClass, octave: prevOctave } = noteToObject(prevNote);
// const pcIndex = getPitchClassIndex(normalizePitchClass(pitchClass));
// const prevPcIndex = getPitchClassIndex(normalizePitchClass(pitchClasses[i - 1]));
// if(pitchClass[0] === 'C' && prevPitchClass[0] === 'B') {
// notes[i] = `${pitchClass}${octave + 1}` as Note;
// }
// }
// return notes;
let currentOctave = octave;
let wasNextOctave = false;

return pitchClasses.map((pitchClass, i) => {
const notes = pitchClasses.map((pitchClass, i) => {
if(i !== 0) {
const pcIndex = getPitchClassIndex(normalizePitchClass(pitchClass));
const prevPcIndex = getPitchClassIndex(normalizePitchClass(pitchClasses[i - 1]));
// Checking if the octave needs to be incremented:
// We can know it definitely needs to be incremented if the current pitch class is C.
// Otherwise, we need to check if the current pitch class pitch class index is smaller than the previous one, as that would indicate that weve passed the B pitch class.
if((pcIndex < prevPcIndex && pitchClass[0] !== 'B') || pitchClass[0] === 'C') {
// We can know it definitely needs to be incremented if the current *ABSOLUTE* pitch class is C and the raw pitch class is not B.
const isC = normalizePitchClass(pitchClass) === 'C' && pitchClass[0] !== 'B';
// Otherwise, we need to check if the current pitch class index is smaller than the previous one, as that would indicate that we've passed the B pitch class.
const isPassedB = pcIndex < prevPcIndex && pitchClass[0] !== 'B';
// It should also be incremented if the raw pitch class is from the next octave regardless of the pitch class.
const isNextOctave = ['C', 'D'].includes(pitchClass[0]) && ['A', 'B'].includes(pitchClasses[i - 1][0]);
if((isPassedB || (isC) || isNextOctave) && !wasNextOctave) {
currentOctave++;
}
if(wasNextOctave) {
wasNextOctave = false;
}
if(isNextOctave) {
wasNextOctave = true;
}
}
return `${pitchClass}${currentOctave}`;
}) as Array<Note>;
return notes;
}

/**
Expand Down
36 changes: 34 additions & 2 deletions src/utilities/PureMusicUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,34 @@ import {
NoteAsObject, PitchClass, PitchClassLetter, Accidental, Note, Octave, RawPitchClass, RawFlatPitchClass, RawSharpPitchClass,
} from '../types';

/**
* Returns an array with numbers that represent the index of each pitch class as a number.
* @param pitchClasses
* @returns
*/
export function pitchClassesToNumbers(pitchClasses: Array<PitchClass>) {
return pitchClasses.map(pc => getPitchClassIndex(pc));
}

/**
* Returns an array of notes with a specific octave.
* @param {Array} pitchClasses Array of pitch classes.
* @param {number} octave Octave to assign to notes..
* @returns {Array}
*/
export function pitchClassesToNotes(pitchClasses: Array<PitchClass>, octave: number): Array<Note> {
return pitchClasses.map(pitchClass => `${pitchClass}${octave}` as Note);
}

/**
*
* @param notes
* @returns
*/
export function notesToPitchClasses(notes: Array<Note>) {
return notes.map(note => noteToObject(note as Note).pitchClass);
}

/**
* Calculate the pure interval between 2 pitch classes.
* @param {PitchClass} pitchClass1 first note
Expand Down Expand Up @@ -157,9 +185,13 @@ export function normalizeNote(note: Note): Note {
const { pitchClass, octave } = noteToObject(note);
const normalizedPitchClass = normalizePitchClass(pitchClass);
let octaveDifference = 0;
if(pitchClass[0] === 'B' && pitchClass.includes('#' || 'x')) {
const bWithSharps = pitchClass[0] === 'B' && pitchClass.includes('#' || 'x');
const aWithSharps = pitchClass === 'A#x';
const cWithFlats = pitchClass[0] === 'C' && pitchClass.includes('b');
const dWithFlats = pitchClass === 'Dbbb';
if(bWithSharps || aWithSharps) {
octaveDifference = 1;
} else if(pitchClass[0] === 'C' && pitchClass.includes('b')) {
} else if(cWithFlats || dWithFlats) {
octaveDifference = -1;
}
return `${normalizedPitchClass}${octave + octaveDifference}` as Note;
Expand Down
19 changes: 9 additions & 10 deletions test/utilities/music-functions.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
extractOctave, extractPitchClass, getPitchClassesInterval, notesInRange, noteToObject,
pitchClassesToNotes, pitchClassesToPianoChordNotes, transposeNote, intervalsToNotes,
pitchClassesToPianoChordNotes, transposeNote, intervalsToNotes,
spellScale,
} from '../../lib/index.js';

Expand Down Expand Up @@ -46,14 +46,6 @@ describe('Music addon functions', () => {
});
});

describe('#pitchClassesToNotes', () => {
it('should return an array of notes when input is valid', () => {
const pitchClasses = ['C', 'E'];
const stub = ['C3', 'E3'];
expect(pitchClassesToNotes(pitchClasses, 3)).to.eql(stub);
});
});

describe('#pitchClassesToPianoChordNotes', () => {
let g, gChord;
beforeEach(() => {
Expand All @@ -80,7 +72,14 @@ describe('Music addon functions', () => {
expect(pitchClassesToPianoChordNotes(bigChord, 3)).to.eql(stub);
});

it('with a scale', () => {
it('with a flats scale', () => {
const bigChord = spellScale(intervalsToNotes('E', [0, 1, 2, 3, 5, 7, 9, 10]));
const stub = ['E3', 'F3', 'Gb3', 'Abb3', 'Bbb3', 'Cb4', 'Db4', 'Ebb4'];

expect(pitchClassesToPianoChordNotes(bigChord, 3)).to.eql(stub);
});

it('with a sharps scale', () => {
const bigChord = spellScale(intervalsToNotes('E', [0, 3, 4, 6, 8, 9, 11]));
const stub = ['E3', 'Fx3', 'G#3', 'A#3', 'B#3', 'C#4', 'D#4'];

Expand Down
10 changes: 9 additions & 1 deletion test/utilities/pure-music-utils.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
enharmonicPitchClass, getNotesInterval, getPatternFromNotes, getPatternFromPitchClasses, isPitchClass, isNote, normalizePitchClass, toFlat, normalizeNote,
enharmonicPitchClass, getNotesInterval, getPatternFromNotes, getPatternFromPitchClasses, isPitchClass, isNote, normalizePitchClass, toFlat, normalizeNote, pitchClassesToNotes,
} from '../../lib/index.js';

describe('#PureMusicUtils', () => {
Expand Down Expand Up @@ -194,4 +194,12 @@ describe('#PureMusicUtils', () => {
expect(isNote('C')).to.equal(false);
});
});

describe('#pitchClassesToNotes', () => {
it('should return an array of notes when input is valid', () => {
const pitchClasses = ['C', 'E'];
const stub = ['C3', 'E3'];
expect(pitchClassesToNotes(pitchClasses, 3)).to.eql(stub);
});
});
});

0 comments on commit 1fbf6e0

Please sign in to comment.