chordFrettings function
List<Fretting>
chordFrettings(
- Chord chord,
- FrettedInstrument instrument,
- {dynamic highestFret = 4}
Implementation
List<Fretting> chordFrettings(Chord chord, FrettedInstrument instrument,
{highestFret = 4}) {
final int minPitchClasses = chord.intervals.length;
Map<int, Set<FretPosition>> partitionFretsByString() {
final Set<FretPosition> positions =
chordFrets(chord, instrument, highestFret);
final Map<int, Set<FretPosition>> partitions = new Map.fromIterable(
instrument.stringIndices,
key: (index) => index,
value: (_) => new Set<FretPosition>());
for (final position in positions) {
partitions[position.stringIndex]!.add(position);
}
return partitions;
}
// collectFrettingPositions(fretCandidatesPerString) {
// final stringCount = fretCandidatesPerString.length;
// final positionSet = [];
// final fretArray = [];
// void fill(s) {
// if (s == stringCount) {
// positionSet.push fretArray.slice()
// } else {
// for fret in fretCandidatesPerString[s]
// fretArray[s] = fret
// fill s + 1;
// }
// }
// fill(0);
// return positionSet;
// }
// // actually tests pitch classes, not pitches
// containsAllChordPitches(fretArray) {
// final trace = fretArray.join('') == '022100'
// final pitchClasses = []
// for fret, string in fretArray
// continue unless typeof(fret) is 'number'
// pitchClass = instrument.pitchAt({fret, string}).toPitchClass().semitones
// pitchClasses.push pitchClass unless pitchClass in pitchClasses
// return pitchClasses.length == chord.pitches.length
// }
// maximumFretDistance(fretArray) {
// frets = (fret for fret in fretArray when typeof(fret) is 'number')
// // fretArray = (fret for fret in fretArray when fret > 0)
// return Math.max(frets...) - Math.min(frets...) <= 3
// }
Set<Fretting> generateFrettings() {
final frettings = new Set<Fretting>();
final stringFrets = partitionFretsByString();
void collect(Iterable<int> unprocessedStringIndices,
Set<FretPosition> collectedPositions) {
if (unprocessedStringIndices.isEmpty) {
final int pitchClassCount = collectedPositions
.map((position) => position.semitones % 12)
.toSet()
.length;
if (pitchClassCount >= minPitchClasses) {
frettings.add(new Fretting(
chord: chord,
instrument: instrument,
positions: collectedPositions));
}
} else {
final int stringIndex = unprocessedStringIndices.first;
final Iterable<int> futureStringIndices =
unprocessedStringIndices.skip(1);
collect(futureStringIndices, collectedPositions);
for (final position in stringFrets[stringIndex]!) {
final Set<FretPosition> clone = new Set.from(collectedPositions);
clone.add(position);
collect(futureStringIndices, clone);
}
}
}
collect(instrument.stringIndices, new Set<FretPosition>());
// final fretArrays = collectFrettingPositions(fretsPerString());
// fretArrays = fretArrays.filter(containsAllChordPitches);
// fretArrays = fretArrays.filter(maximumFretDistance);
// for fretArray in fretArrays
// positions = ({fret, string} for fret, string in fretArray when typeof(fret) is 'number')
// for pos in positions
// pos.intervalClass = Interval.between(chord.root, instrument.pitchAt(pos))
// pos.degreeIndex = chord.intervals.indexOf(pos.intervalClass)
// sets = [[]]
// sets = collectBarreSets(instrument, fretArray) if positions.length > 4
// for barres in sets
// frettings.push new Fretting {positions, chord, barres, instrument}
return frettings;
}
// final chordNoteCount = chord.pitches.length;
//
// Filters
//
// // really counts distinct pitch classes, not distinct pitches
// countDistinctNotes(fingering) {
// // _.chain(fingering.positions).pluck('intervalClass').uniq().value().length
// final intervalClasses = []
// for {intervalClass} in fingering.positions
// intervalClasses.push intervalClass unless intervalClass in intervalClasses
// return intervalClasses.length;
// }
// hasAllNotes(fingering) =>
// countDistinctNotes(fingering) == chordNoteCount;
// mutedMedialStrings(fingering) =>
// fingering.fretstring.match(/\dx+\d/);
// mutedTrebleStrings(fingering) =>
// fingering.fretstring.match(/x$/);
// getFingerCount(fingering) {
// final n = (pos for pos in fingering.positions when pos.fret > 0).length;
// n -= barre.fingerReplacementCount - 1 for barre in fingering.barres;
// return n;
// }
// fourFingersOrFewer(fingering) =>
// getFingerCount(fingering) <= 4;
// // Construct the filter set
// final filters = [];
// // filters.push name: 'has all chord notes', select: hasAllNotes
// if (options.filter) {
// filters.push name: 'four fingers or fewer', select: fourFingersOrFewer
// }
// if (!options.fingerpicking) {
// filters.push name: 'no muted medial strings', reject: mutedMedialStrings
// filters.push name: 'no muted treble strings', reject: mutedTrebleStrings
// }
// // filter by all the filters in the list, except ignore those that wouldn't pass anything
// filterFrettings(frettings) {
// for {name, select, reject} in filters
// filtered = frettings
// select = ((x) -> not reject(x)) if reject
// filtered = filtered.filter(select) if select
// unless filtered.length
// console.warn "#{chord.name}: no frettings pass filter \"#{name}\"" if warn
// filtered = frettings
// frettings = filtered
// return frettings;
// }
//
// Sort
//
// // FIXME count pitch classes, not sounded strings
// highNoteCount(fingering) =>
// fingering.positions.length;
// isRootPosition(fingering) =>
// _(fingering.positions).sortBy((pos) -> pos.string)[0].degreeIndex == 0;
// reverseSortKey = (fn) -> (a) -> -fn(a)
// // ordered list of preferences, from most to least important
// final preferences = [
// {name: 'root position', key: isRootPosition}
// {name: 'high note count', key: highNoteCount}
// {name: 'avoid barres', key: reverseSortKey((fingering) -> fingering.barres.length)}
// {name: 'low finger count', key: reverseSortKey(getFingerCount)}
// ];
int Function(T, T) compareBy<T>(int Function(T) f, {bool reverse = false}) =>
reverse ? (a, b) => f(a) - f(b) : (a, b) => f(b) - f(a);
List<Fretting> sortFrettings(Iterable<Fretting> frettingSet) {
final List<Fretting> frettingList = frettingSet.toList();
// number of open strings:
insertionSort<Fretting>(frettingList,
compare: compareBy<Fretting>((Fretting f) =>
f.positions.where((pos) => pos.fretNumber == 0).length));
// number of sounded strings:
insertionSort(frettingList,
compare: compareBy<Fretting>((f) => f.positions.length));
// root position:
insertionSort(frettingList,
compare: compareBy<Fretting>((f) => f.inversionIndex, reverse: true));
return frettingList;
}
//
// Generate, filter, and sort
//
final frettings = generateFrettings();
// frettings = filterFrettings(frettings);
final orderedFrettings = sortFrettings(frettings);
// final properties = {
// 'root': isRootPosition
// 'barres': (f) -> f.barres.length
// 'fingers': getFingerCount
// 'inversion': (f) -> f.inversionLetter or ''
// // 'bass': /^\d{3}x*$/
// // 'treble': /^x*\d{3}$/
// 'skipping': /\dx+\d/
// 'muting': /\dx/
// 'open': /0/
// 'triad': ({positions}) -> positions.length == 3
// 'position': ({positions}) -> Math.max(_.min(_.pluck(positions, 'fret')) - 1, 0)
// 'strings': ({positions}) -> positions.length
// };
// for name, fn of properties
// for fingering in orderedFrettings
// value = if fn instanceof RegExp then fn.test(fingering.fretstring) else fn(fingering)
// fingering.properties[name] = value
return orderedFrettings;
}