VocalEasel/Sources/VLPitchGrid.cpp

252 lines
6.4 KiB
C++
Raw Permalink Normal View History

//
// File: VLPitchGrid.h - Translate between (MIDI) pitches and their vertical position
//
// Author(s):
//
// (MN) Matthias Neeracher
//
// Copyright © 2011 Matthias Neeracher
//
#include "VLPitchGrid.h"
#include "VLModel.h"
#define P(x) (1<<x)
static inline bool IsBasicNote(int semi)
{
//
// Bitmap of basic notes, padded left and right by two positions
// B C D E F G A B C
const uint16_t kBasicNotes = P( 1)|P( 2)|P( 4)|P( 6)|P( 7)|P( 9)|P(11)|P(13)|P(14);
return kBasicNotes & (1<<(semi+2));
}
static inline bool HasFlat(int semi, int key)
{
const uint16_t kFMajor = P(1) | P(13); // Bb
const uint16_t kBbMajor = kFMajor | P(6); // Eb
const uint16_t kEbMajor = kBbMajor | P(11); // Ab
const uint16_t kAbMajor = kEbMajor | P(4); // Db
const uint16_t kDbMajor = kAbMajor | P(9); // Gb
const uint16_t kGbMajor = kDbMajor | P(2) | P(14); // Cb
static const uint16_t sFlats[] =
{kFMajor, kBbMajor, kEbMajor, kAbMajor, kDbMajor, kGbMajor};
return sFlats[-1-key] & (1<<(semi+2));
}
static inline bool HasSharp(int semi, int key)
{
const uint16_t kGMajor = P(7); // F#
const uint16_t kDMajor = kGMajor | P(2) | P(14); // C#
const uint16_t kAMajor = kDMajor | P(9); // G#
const uint16_t kEMajor = kAMajor | P(4); // D#
const uint16_t kBMajor = kEMajor | P(11); // A#
const uint16_t kFsMajor = kBMajor | P(6); // E#
static const uint16_t sSharps[] =
{kGMajor, kDMajor, kAMajor, kEMajor, kBMajor, kFsMajor};
return sSharps[key-1] & (1<<(semi+2));
}
uint16_t VLVisualInKey(int8_t pitch, int key)
{
if (key < 0 && HasFlat(pitch % 12, key))
return VLNote::kWantFlat;
else if (key > 0 && HasSharp(pitch % 12, key))
return VLNote::kWantSharp;
else
return 0;
}
static inline int8_t SemiToStep(int semi)
{
static const int8_t sSteps[] =
// Bb B C D E F G A B C
{-1,-1,0,0,1,1,2,3,3,4,4,5,5,6,7,7};
return sSteps[semi+2];
}
static inline int8_t StepToSemi(int step)
{
// C D E F G A B
static const int8_t sSemi[] = {0,2,4,5,7,9,11};
return sSemi[step];
}
uint16_t VLPitchAccidental(int8_t pitch, uint16_t visual, int key)
{
2011-08-31 02:02:10 +00:00
int semi = pitch % 12;
2011-09-05 22:20:48 +00:00
//
// The user expressed a preference, try to match it
//
switch (visual & VLNote::kAccidentalsMask) {
case VLNote::kWantNatural:
if (IsBasicNote(semi))
return visual;
break;
case VLNote::kWant2Flat:
if (IsBasicNote(semi+2))
return visual;
else
return VLNote::kWantFlat;
case VLNote::kWantFlat:
if (IsBasicNote(semi+1))
return visual;
break;
case VLNote::kWant2Sharp:
if (IsBasicNote(semi-2))
return visual;
else
return VLNote::kWantSharp;
case VLNote::kWantSharp:
if (IsBasicNote(semi-1))
return visual;
break;
default:
break;
}
//
// No visuals, or no match
//
2011-08-31 02:02:10 +00:00
visual &= ~VLNote::kAccidentalsMask;
if (IsBasicNote(semi))
return visual | VLNote::kWantNatural;
else if (key <= 0)
return visual | VLNote::kWantFlat;
else
return visual | VLNote::kWantSharp;
}
int VLPitchToGrid(int8_t pitch, uint16_t visual, int key)
{
int semi = pitch % 12;
int octave = (pitch/12)-5;
switch (visual & VLNote::kAccidentalsMask) {
case VLNote::kWantNatural:
break;
case VLNote::kWant2Flat:
semi += 2;
break;
case VLNote::kWantFlat:
semi += 1;
break;
case VLNote::kWant2Sharp:
semi -= 2;
break;
case VLNote::kWantSharp:
semi -= 1;
break;
default:
if (IsBasicNote(semi)) {
;
} else if (key <= 0) {
semi += 1;
} else {
semi -= 1;
}
break;
}
return SemiToStep(semi)+7*octave;
}
int8_t VLGridToPitch(int gridPos, uint16_t visual, int key)
{
int octave = VLNote::kMiddleC;
while (gridPos > 6) {
octave += 12;
gridPos -= 7;
}
while (gridPos < 0) {
octave -= 12;
gridPos += 7;
}
int semi = StepToSemi(gridPos);
int accidental;
switch (visual) {
case VLNote::kWantFlat:
accidental = -1;
break;
case VLNote::kWant2Flat:
accidental = -2;
break;
case VLNote::kWantSharp:
accidental = 1;
break;
case VLNote::kWant2Sharp:
accidental = 2;
break;
case VLNote::kWantNatural:
accidental = 0;
break;
default:
if (key > 0 && HasSharp(semi, key))
accidental = 1;
else if (key < 0 && HasFlat(semi, key))
accidental = -1;
else
accidental = 0;
break;
}
return octave+semi+accidental;
}
void VLVisualFilter::ResetWithKey(int key)
{
memset(&fState[0], 0, 7*sizeof(fState[0]));
switch (key) { // Almost every state falls through
case -6:
fState[0] = VLNote::kWantFlat;
case -5:
2011-09-05 22:21:13 +00:00
fState[4] = VLNote::kWantFlat;
case -4:
fState[1] = VLNote::kWantFlat;
case -3:
2011-09-05 22:21:13 +00:00
fState[5] = VLNote::kWantFlat;
case -2:
fState[2] = VLNote::kWantFlat;
case -1:
fState[6] = VLNote::kWantFlat;
case 0:
break;
case 6:
fState[2] = VLNote::kWantSharp;
case 5:
fState[5] = VLNote::kWantSharp;
case 4:
fState[1] = VLNote::kWantSharp;
case 3:
fState[4] = VLNote::kWantSharp;
case 2:
fState[0] = VLNote::kWantSharp;
case 1:
fState[3] = VLNote::kWantSharp;
default:
break;
}
}
uint16_t VLVisualFilter::operator()(int gridPos, uint16_t visual)
{
2011-08-31 02:02:10 +00:00
uint16_t acc = visual & VLNote::kAccidentalsMask;
gridPos = (gridPos+700) % 7;
if (acc == VLNote::kWantNatural)
acc = 0;
if (acc != fState[gridPos]) {
fState[gridPos] = acc;
if (!acc)
acc = VLNote::kWantNatural;
} else {
2011-08-31 02:02:10 +00:00
acc = 0;
}
2011-08-31 02:02:10 +00:00
return (visual & ~VLNote::kAccidentalsMask) | acc;
}