mirror of
https://github.com/microtherion/VocalEasel.git
synced 2024-12-22 19:23:59 +00:00
123 lines
3.7 KiB
C++
123 lines
3.7 KiB
C++
|
//
|
||
|
// File: VLPitchName.cpp - Translate between (MIDI) pitches and their UTF-8 representation
|
||
|
//
|
||
|
// Author(s):
|
||
|
//
|
||
|
// (MN) Matthias Neeracher
|
||
|
//
|
||
|
// Copyright © 2011 Matthias Neeracher
|
||
|
//
|
||
|
|
||
|
#include "VLPitchName.h"
|
||
|
#include "VLModel.h"
|
||
|
|
||
|
const char * kVLSharpStr = "\xE2\x99\xAF";
|
||
|
const char * kVLFlatStr = "\xE2\x99\xAD";
|
||
|
const char * kVL2SharpStr = "\xF0\x9D\x84\xAA";
|
||
|
const char * kVL2FlatStr = "\xF0\x9D\x84\xAB";
|
||
|
const char * kVLNaturalStr = "\xE2\x99\xAE";
|
||
|
|
||
|
static const char kScale[] = "C D EF G A B";
|
||
|
static const char * kFancyAccidental[] =
|
||
|
{
|
||
|
kVL2SharpStr, kVLSharpStr, "", kVLFlatStr, kVL2FlatStr
|
||
|
};
|
||
|
const int8_t kAccidentalBase = 2;
|
||
|
|
||
|
std::string VLPitchName(int8_t pitch, uint16_t accidental)
|
||
|
{
|
||
|
if (pitch == VLNote::kNoPitch)
|
||
|
return "r";
|
||
|
int8_t adjust;
|
||
|
accidental &= VLNote::kAccidentalsMask;
|
||
|
switch (accidental) {
|
||
|
case VLNote::kWant2Flat:
|
||
|
adjust = 2;
|
||
|
break;
|
||
|
case VLNote::kWantFlat:
|
||
|
adjust = 1;
|
||
|
break;
|
||
|
case VLNote::kWantSharp:
|
||
|
adjust = -1;
|
||
|
break;
|
||
|
case VLNote::kWant2Sharp:
|
||
|
adjust = -2;
|
||
|
break;
|
||
|
default:
|
||
|
adjust = 0;
|
||
|
break;
|
||
|
}
|
||
|
pitch += adjust;
|
||
|
pitch %= 12;
|
||
|
//
|
||
|
// Will either succeed immediately, or after one adjustment
|
||
|
//
|
||
|
if (kScale[pitch] == ' ')
|
||
|
if (adjust < 0 || (accidental & VLNote::kPreferFlats) == VLNote::kPreferFlats) {
|
||
|
++adjust;
|
||
|
pitch = (pitch+1)%12;
|
||
|
} else {
|
||
|
--adjust;
|
||
|
pitch = (pitch+11)%12;
|
||
|
}
|
||
|
std::string name = std::string(1, kScale[pitch]);
|
||
|
if (adjust)
|
||
|
return name+kFancyAccidental[adjust+kAccidentalBase];
|
||
|
else if ((accidental & VLNote::kWantNatural) == VLNote::kWantNatural)
|
||
|
return name+kVLNaturalStr;
|
||
|
else
|
||
|
return name;
|
||
|
}
|
||
|
|
||
|
static bool TestAccidental(uint16_t acc, int8_t adjust, std::string & str, size_t at,
|
||
|
const char * fancyStr, const char * asciiString,
|
||
|
int8_t & pitch, uint16_t * accidental)
|
||
|
{
|
||
|
size_t sz = str.size()-at;
|
||
|
size_t fancySz = strlen(fancyStr);
|
||
|
if (sz >= fancySz && !memcmp(fancyStr, &str[at], fancySz)) {
|
||
|
pitch += adjust;
|
||
|
*accidental = acc;
|
||
|
str.erase(at, fancySz);
|
||
|
return true;
|
||
|
}
|
||
|
size_t asciiSz = strlen(asciiString);
|
||
|
if (!asciiSz || sz < asciiSz)
|
||
|
return false;
|
||
|
|
||
|
for (size_t cmp = at; *asciiString; )
|
||
|
if (std::toupper(str[cmp++]) != *asciiString++)
|
||
|
return false;
|
||
|
pitch += adjust;
|
||
|
*accidental = acc;
|
||
|
str.erase(at, asciiSz);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
int8_t VLParsePitch(std::string & str, size_t at, uint16_t * accidental)
|
||
|
{
|
||
|
int8_t pitch = VLNote::kNoPitch;
|
||
|
//
|
||
|
// Determine key
|
||
|
//
|
||
|
if (const char * key = strchr(kScale, std::toupper(str[at]))) {
|
||
|
pitch = key-kScale+VLNote::kMiddleC;
|
||
|
} else if (str[at] == 'r' || str[at] == 's') {
|
||
|
str.erase(at, 1); // Rest
|
||
|
return VLNote::kNoPitch;
|
||
|
} else
|
||
|
return kPitchError;
|
||
|
str.erase(at, 1);
|
||
|
//
|
||
|
// Look for accidentals
|
||
|
//
|
||
|
TestAccidental(VLNote::kWant2Flat, -2, str, at, kVL2FlatStr, "BB", pitch, accidental) ||
|
||
|
TestAccidental(VLNote::kWantFlat, -1, str, at, kVLFlatStr, "B", pitch, accidental) ||
|
||
|
TestAccidental(VLNote::kWantNatural, 0, str, at, kVLNaturalStr, "", pitch, accidental) ||
|
||
|
TestAccidental(VLNote::kWant2Sharp, 2, str, at, kVL2SharpStr, "##", pitch, accidental) ||
|
||
|
TestAccidental(VLNote::kWantSharp, 1, str, at, kVLSharpStr, "#", pitch, accidental) ||
|
||
|
(*accidental = 0);
|
||
|
|
||
|
return pitch;
|
||
|
}
|