addon/utils/note-methods.js
import Ember from 'ember';
import { arraySwap, flatten } from './array-methods';
const {
A
} = Ember;
/**
* @public
* @class utils
*/
/**
* Sorts an array of {{#crossLink "Note"}}Notes{{/crossLink}} so that they are in the same order that they would
* appear on a piano.
*
* @param {array} notes An array of notes that should be musically-sorted.
*
* @public
* @method sortNotes
*
* @return {array} Array of musically-sorted notes.
*/
export function sortNotes(notes) {
// get octaves so that we can sort based on them
let sortedNotes = extractOctaves(notes);
// Each octave has tons of duplicates
sortedNotes = stripDuplicateOctaves(sortedNotes);
// Create array of arrays. Each inner array contains all the notes in an octave
sortedNotes = createOctavesWithNotes(sortedNotes);
// Sort the notes in each octave, alphabetically, flats before naturals
sortedNotes = octaveSort(sortedNotes);
// Determine last note of first octave, then for each octave, split at
// that note, then shift the beginning notes to the end
sortedNotes = octaveShift(sortedNotes);
// Flatten array of arrays into a flat array
return A(flatten(sortedNotes));
}
/**
* Takes an array of arrays of notes, determines the last note of
* the first array, then splits the rest of the arrays in the array at the last
* note of the first array, and moves the beginning of the array to the end
* so that each array starts at the next note after the last note of the first
* array, instead of at "A" (alphabetically).
*
* @example
* This is hard to explain. Here's an example.
* (Simplified, as the real notes are objects)
*
* Example input: [['A0', 'B0'], ['A1', 'B1', 'C1', 'D1']]
* Example output: [['A0', 'B0'], ['C1', 'D1', 'A1', 'B1']]
*
* @private
* @method octaveShift
*
* @param {array} octaves An array of octaves, each octave is an array of Notes.
*
* @return {array} Input array after having been shifted.
*/
export function octaveShift(octaves) {
// Pull first octave from beginning of array
const firstOctave = A(A(octaves).shiftObject());
// Get all the note names from the second octave for comparison
const secondOctaveNames = A(octaves.get('firstObject')).getEach('name');
// Get the note name of the last note in the first octave
const lastNote = firstOctave.get('lastObject.name');
// Get the index of the occurrence of the last note from the first
// octave, in the second octave
const indexToShiftAt = secondOctaveNames.lastIndexOf(lastNote) + 1;
// Split the octave array at that point, and move the first chunk to the end
return A(octaves.map((octave) => arraySwap(octave, indexToShiftAt)))
// Put first octave back at the beginning of the array
.unshiftObjects([firstOctave]);
}
/**
* Maps through an array of arrays and sorts each array with
* "noteSort"
*
* @private
* @method octaveSort
*
* @param {array} octaves array of arrays to be sorted
*
* @return {array} array of sorted arrays
*/
export function octaveSort(octaves) {
return octaves.map((octave) => octave.sort(noteSort));
}
/**
* Accepts an array of Note objects and passes back an array
* like this: [original array, array of each octave in the orginal array]
*
* @private
* @method extractOctaves
*
* @param {array} notes array of note objects.
*
* @return {array} array containing two inner arrays, [0] is the untouched input
* array, [1] is an array of all the octaves in the original array.
*/
export function extractOctaves(notes) {
return [ notes, A(A(notes).getEach('octave')) ];
}
/**
* Accepts an array of two arrays and returns the same
* array, but with array at index [1] uniq'd and sorted alphabetically.
*
* @private
* @method stripDuplicateOctaves
*
* @param {array} [ notes, octaves ] the output from extractOctaves.
*
* @return {array} The mutated array.
*/
export function stripDuplicateOctaves([ notes, octaves ]) {
return [ notes, A(octaves).uniq().sort() ];
}
/**
* Accepts an array of two arrays, [0] being an array
* of Note objects, [1] being all the available octaves. Returns a single array
* made up of arrays of Note objects, organized by octave. Each inner array
* represents all of the notes in an octave.
*
* @private
* @method createOctavesWithNotes
*
* @param {array} data The output of stripDuplicateOctaves.
*
* @return {Ember.MutableArray}
*/
export function createOctavesWithNotes([ notes, octaves ]) {
return A(octaves).map((octave) => A(notes).filterBy('octave', octave));
}
/**
* Acts as a comparator function for the
* {{#crossLink "Array/sort:method"}}Array.prototype.sort{{/crossLink}} method.
* Sorts two {{#crossLink "Note"}}{{/crossLink}} instances alphabetically, flats
* before naturals.
*
* @private
* @method noteSort
*
* @param {Note} a The first Note instance to compare.
* @param {Note} b The second Note instance to compare.
*
* @return {number} -1 or 1, depending on whether the current
* {{#crossLink "Note"}}{{/crossLink}} instance should be sorted left, or right.
*/
export function noteSort(a, b) {
const aLet = a.get('letter');
const bLet = b.get('letter');
if (aLet < bLet) {
return -1;
}
if (aLet === bLet) {
if (a.get('accidental') === 'b') {
return -1;
}
}
return 1;
}