// Copyright Epic Games, Inc. All Rights Reserved. #include "DSP/MidiNoteQuantizer.h" #include "Math/NumericLimits.h" namespace Audio { namespace Note { static const float B1 = 11.0f; static const float Bb1 = 10.0f; static const float A1 = 9.0f; static const float Ab1 = 8.0f; static const float G1 = 7.0f; static const float Gb1 = 6.0f; static const float F1 = 5.0f; static const float E1 = 4.0f; static const float Eb1 = 3.0f; static const float D1 = 2.0f; static const float Db1 = 1.0f; static const float C1 = 0.0f; } ScaleDegreeSet::ScaleDegreeSet(const TArray& InScaleDegrees, const TArray& InChordTones) : ScaleDegrees(InScaleDegrees) , ChordTones(InChordTones) { } TArrayView ScaleDegreeSet::GetScaleDegreeSet(bool bChordTonesOnlyIfApplicable) { if (bChordTonesOnlyIfApplicable && ChordTones.Num()) { return ChordTones; } return ScaleDegrees; } float FMidiNoteQuantizer::QuantizeMidiNote(const float InNote, const float InRoot, EMusicalScale::Scale InScale, bool bChordTonesOnlyIfApplicable) { return QuantizeMidiNote(InNote, InRoot, ScaleDegreeSetMap[InScale].GetScaleDegreeSet(bChordTonesOnlyIfApplicable)); } float FMidiNoteQuantizer::QuantizeMidiNote(const float InNote, const float InRoot, const TArrayView InScaleDegrees, const float InSemitoneScaleRange) { // QuantizeValueToScaleDegree() works within a single octave, so map incoming note to within the first octave const float NoteRootDelta = (InNote - InRoot); const float ClampedSemitoneScaleRange = FMath::Max(InSemitoneScaleRange, 1.0f); const float InNoteOctave = FMath::FloorToFloat(NoteRootDelta / ClampedSemitoneScaleRange); const float ValueToQuant = NoteRootDelta - (InSemitoneScaleRange * InNoteOctave); const float QuantizedValue = QuantizeValueToScaleDegree(ValueToQuant, InScaleDegrees); // reconstruct the quantized midi note given the InRoot and original octave return (InSemitoneScaleRange * InNoteOctave + QuantizedValue) + InRoot; } float FMidiNoteQuantizer::QuantizeValueToScaleDegree(const float InValue, const TArrayView InScaleDegrees, const float InSemitoneScaleRange) { // the Set we are quantizing to should have at least 2 elements if (!InScaleDegrees.Num()) { return InValue; } // First check the min delta against the scale range float QuantizedValue = InSemitoneScaleRange; float CurrMinDelta = FMath::Abs(InValue - InSemitoneScaleRange); // Then check the given scale degree array for (int32 i = 0; i < InScaleDegrees.Num(); ++i) { float NewDelta = FMath::Abs(InValue - InScaleDegrees[i]); if (NewDelta < CurrMinDelta) { CurrMinDelta = NewDelta; QuantizedValue = InScaleDegrees[i]; } } return QuantizedValue; } // Initialize our note & chord arrays /* REQUIREMENTS: * The (optional) second array defines Chord Tones, if empty, the first array will be used always * - this is ideally a subset of the first array (user-facing UI/UX will imply this), but does not technically need to be and is not enforced. * * The first array MUST have at least 1 entry or it will ensure. * * Both array MUST be in ascending order, or quantization behavior will be broken. This is not enforced at runtime and quantization and will not warn/ensure * * The Arrays must represent a full octave (inclusive): the lowest element being (0.0 and the highest being 12.0) */ TMap FMidiNoteQuantizer::ScaleDegreeSetMap{ /* entry example: * { EMusicalScale::Scale::[SCALE NAME], {{ [(required) SCALE DEGREES] }, { [(optional) CHORD TONES] }}} */ { EMusicalScale::Scale::Major, {{ Note::C1, Note::D1, Note::E1, Note::F1, Note::G1, Note::A1, Note::B1 }, { Note::C1, Note::E1, Note::G1, Note::B1 }}} , { EMusicalScale::Scale::Dominant7th_Mixolydian, {{ Note::C1, Note::D1, Note::E1, Note::F1, Note::G1, Note::A1, Note::Bb1 }, { Note::C1, Note::E1, Note::G1, Note::Bb1 }}} , { EMusicalScale::Scale::Minor_Dorian, {{ Note::C1, Note::D1, Note::Eb1, Note::F1, Note::G1, Note::A1, Note::Bb1 }, { Note::C1, Note::Eb1, Note::G1, Note::Bb1 }}} , { EMusicalScale::Scale::HalfDiminished_Locrian, {{ Note::C1, Note::Db1, Note::Eb1, Note::F1, Note::Gb1, Note::Ab1, Note::Bb1 }, { Note::C1, Note::Eb1, Note::Gb1, Note::Bb1 }}} , { EMusicalScale::Scale::Diminished, {{ Note::C1, Note::D1, Note::Eb1, Note::F1, Note::Gb1, Note::Ab1, Note::A1, Note::B1 }, { Note::C1, Note::Eb1, Note::Gb1, Note::A1 }}} , { EMusicalScale::Scale::MajorPentatonic, {{ Note::C1, Note::D1, Note::E1, Note::G1, Note::A1}, { Note::C1, Note::E1, Note::G1 }}} , { EMusicalScale::Scale::Lydian, {{ Note::C1, Note::D1, Note::E1, Note::Gb1, Note::G1, Note::A1, Note::B1}, { Note::C1, Note::E1, Note::G1, Note::B1}}} , { EMusicalScale::Scale::Bebop_Major, {{ Note::C1, Note::D1, Note::E1, Note::Gb1, Note::G1, Note::Ab1, Note::A1, Note::B1}, { Note::C1, Note::E1, Note::G1, Note::B1}}} , { EMusicalScale::Scale::HarmonicMajor, {{ Note::C1, Note::D1, Note::E1, Note::F1, Note::G1, Note::Ab1, Note::B1}, { Note::C1, Note::E1, Note::G1, Note::B1}}} , { EMusicalScale::Scale::LydianAugmented, {{ Note::C1, Note::D1, Note::E1, Note::Gb1, Note::Ab1, Note::A1, Note::B1}, { Note::C1, Note::E1, Note::Ab1, Note::B1}}} , { EMusicalScale::Scale::Augmented, {{ Note::C1, Note::Eb1, Note::E1, Note::G1, Note::Ab1, Note::B1}, { Note::C1, Note::E1, Note::Ab1, Note::B1}}} , { EMusicalScale::Scale::SixthModeOfHarmonicMinor, {{ Note::C1, Note::Eb1, Note::E1, Note::Gb1, Note::G1, Note::A1, Note::B1}, { Note::C1, Note::E1, Note::G1, Note::B1}}} , { EMusicalScale::Scale::Diminished_BeginWithHalfStep, {{ Note::C1, Note::Db1, Note::Eb1, Note::E1, Note::Gb1, Note::G1, Note::A1, Note::Bb1}, { Note::C1, Note::E1, Note::G1, Note::Bb1}}} , { EMusicalScale::Scale::Blues, {{ Note::C1, Note::Eb1, Note::F1, Note::Gb1, Note::G1, Note::Bb1}, { Note::C1, Note::Eb1, Note::G1, Note::Bb1}}} , { EMusicalScale::Scale::Bebop_Dominant, {{ Note::C1, Note::D1, Note::E1, Note::F1, Note::G1, Note::A1, Note::Bb1, Note::B1}, { Note::C1, Note::E1, Note::G1, Note::B1}}} , { EMusicalScale::Scale::Spanish_or_Jewish, {{ Note::C1, Note::Db1, Note::E1, Note::F1, Note::G1, Note::Ab1, Note::Bb1}, { Note::C1, Note::E1, Note::G1, Note::Bb1}}} , { EMusicalScale::Scale::LydianDominant, {{ Note::C1, Note::D1, Note::E1, Note::Gb1, Note::G1, Note::A1, Note::Bb1}, { Note::C1, Note::E1, Note::G1, Note::Bb1}}} , { EMusicalScale::Scale::Hindu, {{ Note::C1, Note::D1, Note::E1, Note::F1, Note::G1, Note::Ab1, Note::Bb1}, { Note::C1, Note::E1, Note::G1, Note::Bb1}}} , { EMusicalScale::Scale::WholeTone, {{ Note::C1, Note::D1, Note::E1, Note::Gb1, Note::Ab1, Note::Bb1}, { Note::C1, Note::E1, Note::Gb1, Note::Bb1}}} , { EMusicalScale::Scale::Chromatic, {{ Note::C1, Note::Db1, Note::D1, Note::Eb1, Note::E1, Note::F1, Note::Gb1, Note::G1, Note::Ab1, Note::A1, Note::Bb1, Note::B1}, {Note::C1, Note::D1, Note::E1, Note::Gb1, Note::Ab1, Note::Bb1 /* Every other note */}}} , { EMusicalScale::Scale::DiminishedWholeTone, {{ Note::C1, Note::Db1, Note::Eb1, Note::E1, Note::Gb1, Note::Ab1, Note::Bb1}, { Note::C1, Note::Eb1, Note::Gb1, Note::Bb1}}} , { EMusicalScale::Scale::MinorPentatonic, {{ Note::C1, Note::Eb1, Note::F1, Note::G1, Note::Bb1}, { Note::C1, Note::Eb1, Note::G1, Note::Bb1}}} , { EMusicalScale::Scale::Bebop_Minor, {{ Note::C1, Note::D1, Note::Eb1, Note::F1, Note::G1, Note::Ab1, Note::A1, Note::B1}, { Note::C1, Note::Eb1, Note::G1, Note::Bb1}}} , { EMusicalScale::Scale::MelodicMinor, {{ Note::C1, Note::D1, Note::Eb1, Note::F1, Note::G1, Note::A1, Note::B1}, { Note::C1, Note::Eb1, Note::G1, Note::B1}}} , { EMusicalScale::Scale::Bebop_MinorNumber2, {{ Note::C1, Note::D1, Note::Eb1, Note::F1, Note::G1, Note::Ab1, Note::A1, Note::B1}, { Note::C1, Note::Eb1, Note::G1, Note::B1}}} , { EMusicalScale::Scale::HarmonicMinor, {{ Note::C1, Note::D1, Note::Eb1, Note::F1, Note::G1, Note::Ab1, Note::B1}, { Note::C1, Note::Eb1, Note::G1, Note::B1}}} , { EMusicalScale::Scale::Diminished_BeginWithWholeStep, {{ Note::C1, Note::D1, Note::Eb1, Note::F1, Note::Gb1, Note::Ab1, Note::A1, Note::B1}, { Note::C1, Note::Eb1, Note::Gb1, Note::B1}}} , { EMusicalScale::Scale::Phrygian, {{ Note::C1, Note::Db1, Note::Eb1, Note::F1, Note::G1, Note::Ab1, Note::Bb1}, { Note::C1, Note::Eb1, Note::G1, Note::Bb1}}} , { EMusicalScale::Scale::NaturalMinor_Aeolian, {{ Note::C1, Note::D1, Note::Eb1, Note::F1, Note::G1, Note::Ab1, Note::Bb1}, { Note::C1, Note::Eb1, Note::G1, Note::Bb1}}} , { EMusicalScale::Scale::HalfDiminished_LocrianNumber2, {{ Note::C1, Note::D1, Note::Eb1, Note::F1, Note::Gb1, Note::Ab1, Note::Bb1}, { Note::C1, Note::Eb1, Note::Gb1, Note::Bb1}}} }; } // namespace Audio