Successfully round trip double accidentals

This commit is contained in:
Matthias Neeracher 2011-08-28 20:47:25 +02:00
parent a2b1a1e991
commit ffdc41e1d7
9 changed files with 175 additions and 143 deletions

View File

@ -348,6 +348,10 @@ class MusicXMLListener
@note['visual'] = VL::WantSharp
when 'flat'
@note['visual'] = VL::WantFlat
when 'double-sharp'
@note['visual'] = VL::Want2Sharp
when 'flat-flat'
@note['visual'] = VL::Want2Flat
end
when 'root-step'
@note['pitch'] = PITCH[@text]

View File

@ -9,7 +9,6 @@ require 'rexml/document'
INPUT = readPlist(INFILE)
$USE_FLATS = false
$DIVISIONS = 3
def newTextElement(name, text)
@ -59,7 +58,6 @@ def _part_list
end
def _attributes(prop)
$USE_FLATS = prop['key'] <= 0
$DIVISIONS = prop['divisions']
attr = REXML::Element.new('attributes')
attr.add_element newTextElement('divisions', prop['divisions'])
@ -100,28 +98,23 @@ def _tempo(tempo)
return dir
end
STEPS = 'C DbD EbE F GbG AbA BbB '
STEPS = ' BC D EF G A B C '
def _pitch(name, pitch, preferFlats, prefix="")
oct = pitch/12 - 1
stp = 2*(pitch%12)
step = STEPS[stp]
alt = STEPS[stp+1] == ?b
if alt
if preferFlats
alt = -1
else
step = step == ?A ? ?G : step-1
alt = 1
end
def _pitch(name, pitch, accidental, prefix="")
pitch-= accidental
oct = pitch/12 - 1
stp = (pitch%12)+2
step = STEPS[stp]
if step == 0x20
abort "Pitch #{pitch} not representable"
end
if prefix.length > 0
prefix += "-"
end
pitch= REXML::Element.new(name)
pitch.add_element newTextElement(prefix+'step', step.chr)
if alt
pitch.add_element newTextElement(prefix+'alter', alt)
if accidental != 0
pitch.add_element newTextElement(prefix+'alter', accidental)
end
if prefix.length == 0
pitch.add_element newTextElement('octave', oct)
@ -130,28 +123,28 @@ def _pitch(name, pitch, preferFlats, prefix="")
return pitch
end
def _addAccidental(note, pitch, preferFlats)
stp = 2*(pitch%12)
alt = STEPS[stp+1] == ?b
if alt
note.add_element newTextElement('accidental',
preferFlats ? 'flat' : 'sharp')
end
end
TYPE = %w[whole half quarter eighth 16th 32nd]
ACC = %w[flat-flat flat natural sharp double-sharp]
def _note(pitch, dur, visual, tied)
preferFlats = (visual & VL::WantSharp) == 0 &&
((visual & VL::WantFlat) != 0 || $USE_FLATS)
accidental = nil
case visual & VL::Accidentals
when VL::Want2Flat
accidental = -2
when VL::WantFlat
accidental = -1
when VL::WantNatural
accidental = 0
when VL::WantSharp
accidental = 1
when VL::Want2Sharp
accidental = 2
end
note = REXML::Element.new('note')
if pitch == VL::NoPitch
note.add_element(REXML::Element.new('rest'))
else
if (tied & VL::InChord) != 0
note.add_element 'chord'
end
note.add_element(_pitch('pitch', pitch, preferFlats))
note.add_element(_pitch('pitch', pitch, accidental))
end
note.add_element newTextElement('duration', dur)
if (tied & VL::TiedWithPrev) != 0
@ -162,8 +155,8 @@ def _note(pitch, dur, visual, tied)
end
note.add_element newTextElement('voice', 1)
note.add_element newTextElement('type', TYPE[visual & 7])
if (visual & (VL::WantSharp | VL::WantFlat)) != 0
_addAccidental(note, pitch, preferFlats)
if accidental
note.add_element newTextElement('accidental', ACC[accidental+2])
end
return note

View File

@ -7,9 +7,12 @@ class VL
TiedWithNext = 1
TiedWithPrev = 2
WantSharp = 0x20
WantSharp = 0x10
Want2Sharp = 0x20
WantFlat = 0x40
InChord = 4
Want2Flat = 0x80
WantNatural = 0x50
Accidentals = 0xF0
Unison = 1<<0
Min2nd = 1<<1

View File

@ -10,6 +10,7 @@
#include "VLModel.h"
#include "VLPitchName.h"
#include "VLPitchGrid.h"
#pragma mark class VLFraction
@ -548,6 +549,11 @@ uint8_t & LastTie(VLMeasure & measure)
//
void VLSong::AddNote(VLLyricsNote note, size_t measure, VLFraction at)
{
//
// Sanity check on accidentals
//
note.fVisual = (note.fVisual & ~VLNote::kAccidentalsMask)
| VLPitchAccidental(note.fPitch, note.fVisual, Properties(measure).fKey);
//
// Always keep an empty measure in reserve
//
@ -771,9 +777,17 @@ static void TransposePinned(int8_t & pitch, int semi)
pitch = octave+pitchInOctave;
}
static inline void FlipAccidentals(uint16_t & visual)
{
visual = (visual & ~VLNote::kAccidentalsMask)
| ((visual & VLNote::kPreferSharps) << 2)
| ((visual & VLNote::kPreferFlats) << 2);
}
void VLSong::ChangeKey(int section, int newKey, int newMode, bool transpose)
{
VLProperties & prop = fProperties[section];
bool flipAcc= newKey*prop.fKey < 0;
int semi = 7*(newKey-prop.fKey) % 12;
prop.fKey = newKey;
prop.fMode = newMode;
@ -790,6 +804,10 @@ void VLSong::ChangeKey(int section, int newKey, int newMode, bool transpose)
for (; i!=e; ++i) {
TransposePinned(i->fPitch, semi);
TransposePinned(i->fRootPitch, semi);
if (flipAcc) {
FlipAccidentals(i->fVisual);
FlipAccidentals(i->fRootAccidental);
}
}
}
for (int pass=0; pass<2 && semi;) {
@ -806,6 +824,8 @@ void VLSong::ChangeKey(int section, int newKey, int newMode, bool transpose)
if (i->fPitch == VLNote::kNoPitch)
continue;
i->fPitch += semi;
if (flipAcc)
FlipAccidentals(i->fVisual);
low = std::min(low, i->fPitch);
high = std::max(high, i->fPitch);
}
@ -816,6 +836,7 @@ void VLSong::ChangeKey(int section, int newKey, int newMode, bool transpose)
semi = -12; // Transpose an Octave down
else
break; // Looks like we're done
flipAcc = false;
}
}

View File

@ -10,6 +10,7 @@
#import "VLPListDocument.h"
#import "VLModel.h"
#import "VLPitchGrid.h"
//
// To convert from and to complex file formats, we use ruby scripts operating
@ -40,6 +41,7 @@ protected:
NSMutableArray * fChords;
bool fPerfOrder;
const VLSong * fSong;
VLVisualFilter fVisFilter;
};
NSArray * VLPlistVisitor::EncodeProperties(const std::vector<VLProperties> & properties)
@ -80,6 +82,7 @@ void VLPlistVisitor::VisitMeasure(size_t m, VLProperties & p, VLMeasure & meas)
fNotes = [NSMutableArray arrayWithCapacity:1];
fChords= [NSMutableArray arrayWithCapacity:1];
fVisFilter.ResetWithKey(p.fKey);
VisitNotes(meas, p, true);
VisitChords(meas);
@ -138,13 +141,14 @@ void VLPlistVisitor::VisitNote(VLLyricsNote & n)
nil]
: [NSDictionary dictionary]];
int grid = VLPitchToGrid(n.fPitch, n.fVisual, 0);
NSDictionary * nd =
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:n.fDuration.fNum], @"durNum",
[NSNumber numberWithInt:n.fDuration.fDenom], @"durDenom",
[NSNumber numberWithInt:n.fPitch], @"pitch",
[NSNumber numberWithInt:n.fTied], @"tied",
[NSNumber numberWithInt:n.fVisual], @"visual",
[NSNumber numberWithInt:fVisFilter(grid, n.fVisual)], @"visual",
ly, @"lyrics",
nil];
[fNotes addObject:nd];

View File

@ -80,10 +80,9 @@ static inline int8_t StepToSemi(int step)
return sSemi[step];
}
int VLPitchToGrid(int8_t pitch, uint16_t & visual, int key)
uint16_t VLPitchAccidental(int8_t pitch, uint16_t visual, int key)
{
int semi = pitch % 12;
int octave = (pitch/12)-5;
if ((visual &= VLNote::kAccidentalsMask)) {
//
@ -91,62 +90,73 @@ int VLPitchToGrid(int8_t pitch, uint16_t & visual, int key)
//
switch (visual) {
case VLNote::kWantNatural:
if (!IsBasicNote(semi))
break;
visual = 0; // Don't draw naturals unless needed
goto computePosition;
if (IsBasicNote(semi))
return visual;
break;
case VLNote::kWant2Flat:
if (IsBasicNote(semi+2)) {
semi += 2;
goto computePosition;
}
visual = VLNote::kWantFlat;
goto flatIsPossible;
if (IsBasicNote(semi+2))
return visual;
else
return VLNote::kWantFlat;
case VLNote::kWantFlat:
if (!IsBasicNote(semi+1))
break;
flatIsPossible:
semi += 1;
if (key < 0 && HasFlat(semi, key))
visual = 0;
goto computePosition;
if (IsBasicNote(semi+1))
return visual;
break;
case VLNote::kWant2Sharp:
if (IsBasicNote(semi-2)) {
semi -= 2;
goto computePosition;
}
visual = VLNote::kWantSharp;
goto sharpIsPossible;
if (IsBasicNote(semi-2))
return visual;
else
return VLNote::kWantSharp;
case VLNote::kWantSharp:
if (!IsBasicNote(semi-1))
break;
sharpIsPossible:
semi -= 1;
if (key > 0 && HasSharp(semi, key))
visual = 0;
goto computePosition;
if (IsBasicNote(semi-1))
return visual;
break;
default:
break;
}
}
//
// No visuals, or no match
//
visual = 0;
if (IsBasicNote(semi)) {
if (key < 0 ? HasFlat(semi, key) : key > 0 && HasSharp(semi, key))
visual = VLNote::kWantNatural;
} else if (key < 0) {
semi += 1;
if (!HasFlat(semi, key))
visual = VLNote::kWantFlat;
} else if (key > 0) {
semi -= 1;
if (!HasSharp(semi, key))
visual = VLNote::kWantSharp;
return VLNote::kWantNatural;
} else if (key <= 0) {
return VLNote::kWantFlat;
} else {
semi += 1;
visual = VLNote::kWantFlat;
return 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;
}
computePosition:
return SemiToStep(semi)+7*octave;
}
@ -192,56 +202,52 @@ int8_t VLGridToPitch(int gridPos, uint16_t visual, int key)
return octave+semi+accidental;
}
VLVisualFilter::VLVisualFilter(int key)
void VLVisualFilter::ResetWithKey(int key)
{
memset(&fKeyState[0], 0, 7*sizeof(fKeyState[0]));
memset(&fState[0], 0, 7*sizeof(fState[0]));
switch (key) { // Almost every state falls through
case -6:
fKeyState[0] = VLNote::kWantFlat;
fState[0] = VLNote::kWantFlat;
case -5:
fKeyState[5] = VLNote::kWantFlat;
fState[5] = VLNote::kWantFlat;
case -4:
fKeyState[1] = VLNote::kWantFlat;
fState[1] = VLNote::kWantFlat;
case -3:
fKeyState[4] = VLNote::kWantFlat;
fState[4] = VLNote::kWantFlat;
case -2:
fKeyState[2] = VLNote::kWantFlat;
fState[2] = VLNote::kWantFlat;
case -1:
fKeyState[6] = VLNote::kWantFlat;
fState[6] = VLNote::kWantFlat;
case 0:
break;
case 6:
fKeyState[2] = VLNote::kWantSharp;
fState[2] = VLNote::kWantSharp;
case 5:
fKeyState[5] = VLNote::kWantSharp;
fState[5] = VLNote::kWantSharp;
case 4:
fKeyState[1] = VLNote::kWantSharp;
fState[1] = VLNote::kWantSharp;
case 3:
fKeyState[4] = VLNote::kWantSharp;
fState[4] = VLNote::kWantSharp;
case 2:
fKeyState[0] = VLNote::kWantSharp;
fState[0] = VLNote::kWantSharp;
case 1:
fKeyState[3] = VLNote::kWantSharp;
fState[3] = VLNote::kWantSharp;
default:
break;
}
memcpy(fState, fKeyState, 7*sizeof(fKeyState[0]));
}
uint16_t VLVisualFilter::operator()(int gridPos, uint16_t visual)
{
gridPos %= 12;
if (!visual)
visual = fKeyState[gridPos];
if (visual != fState[gridPos])
if (!fState[gridPos] && visual == VLNote::kWantNatural) {
visual = 0;
} else {
if (!visual)
visual = VLNote::kWantNatural;
fState[gridPos] = visual==VLNote::kWantNatural ? 0 : visual;
}
else
gridPos = (gridPos+60) % 12;
if (visual == VLNote::kWantNatural)
visual = 0;
if (visual != fState[gridPos]) {
fState[gridPos] = visual;
if (!visual)
visual = VLNote::kWantNatural;
} else {
visual = 0;
}
return visual;
}

View File

@ -18,18 +18,19 @@ uint16_t VLVisualInKey(int8_t pitch, int key);
//
// Grid position is defined from middle C
//
int VLPitchToGrid(int8_t pitch, uint16_t & visual, int key);
int8_t VLGridToPitch(int gridPos, uint16_t visual, int key);
uint16_t VLPitchAccidental(int8_t pitch, uint16_t visual, int key);
int VLPitchToGrid(int8_t pitch, uint16_t visual, int key);
int8_t VLGridToPitch(int gridPos, uint16_t visual, int key);
//
// Avoid repeating accidentals
//
class VLVisualFilter {
public:
VLVisualFilter(int key);
VLVisualFilter(int key=0) { ResetWithKey(key); }
void ResetWithKey(int key);
uint16_t operator()(int gridPos, uint16_t visual);
private:
uint16_t fState[7];
uint16_t fKeyState[7];
};

View File

@ -353,7 +353,7 @@
visual:note->fVisual
at:NSMakePoint([self noteXInMeasure:measIdx at:at],
kSystemY)];
uint16_t visual = note->fVisual;
uint16_t visual = VLPitchAccidental(note->fPitch, note->fVisual, kProp.fKey);
pos =
NSMakePoint([self noteXInMeasure:measIdx at:at],
kSystemY+[self noteYInSection:measure.fPropIdx

View File

@ -23,7 +23,7 @@
}
#define TestPitchToGrid(grid,visualOut,pitch,visualIn,key) \
do { uint16_t vis = visualIn; \
do { uint16_t vis = VLPitchAccidental(pitch, visualIn, key); \
STAssertEquals(grid, VLPitchToGrid(pitch,vis,key),\
@"VLPitchToGrid(%d,%02x,%d)", pitch, visualIn, key);\
STAssertEquals((uint16_t)visualOut, vis,\
@ -32,47 +32,47 @@
- (void)testPitchToGrid
{
TestPitchToGrid( 0, 0, 60, 0, 0); // Middle C, C Major
TestPitchToGrid( 0, VLNote::kWantNatural, 60, 0, 0); // Middle C, C Major
TestPitchToGrid( 0, VLNote::kWantNatural, 60, 0, 2); // Middle C, D Major
TestPitchToGrid( 0, 0, 60, 0, -5); // Middle C, Db Major
TestPitchToGrid( 0, VLNote::kWantNatural, 60, 0, -5); // Middle C, Db Major
TestPitchToGrid( 0, VLNote::kWantNatural, 60, 0, -6); // Middle C, Gb Major
TestPitchToGrid( 0, 0, 60, VLNote::kWantNatural, 0);
TestPitchToGrid( 0, VLNote::kWantNatural, 60, VLNote::kWantNatural, 0);
TestPitchToGrid( -1, VLNote::kWantSharp, 60, VLNote::kWantSharp, 0);
TestPitchToGrid( 0, 0, 60, VLNote::kWantFlat, 0);
TestPitchToGrid( 0, VLNote::kWantNatural, 60, VLNote::kWantFlat, 0);
TestPitchToGrid( 0, VLNote::kWantNatural, 60, VLNote::kWantFlat, 2);
TestPitchToGrid( -1, VLNote::kWantSharp, 60, VLNote::kWant2Sharp, 0);
TestPitchToGrid( 1, VLNote::kWant2Flat, 60, VLNote::kWant2Flat, 0);
TestPitchToGrid( 1, VLNote::kWantFlat, 61, 0, 0); // D flat, C Major
TestPitchToGrid( 1, VLNote::kWantFlat, 61, 0, -1); // D flat, F Major
TestPitchToGrid( 1, 0, 61, 0, -4); // D flat, Ab Major
TestPitchToGrid( 1, VLNote::kWantFlat, 61, 0, -4); // D flat, Ab Major
TestPitchToGrid( 0, VLNote::kWantSharp, 61, 0, 1); // D flat, G Major
TestPitchToGrid( 0, 0, 61, 0, 2); // D flat, D Major
TestPitchToGrid( 0, VLNote::kWantSharp, 61, 0, 2); // D flat, D Major
TestPitchToGrid( 1, VLNote::kWantFlat, 61, VLNote::kWantNatural, 0);
TestPitchToGrid( 0, VLNote::kWantSharp, 61, VLNote::kWantSharp, 0);
TestPitchToGrid( 0, 0, 61, VLNote::kWantSharp, 2);
TestPitchToGrid( 0, VLNote::kWantSharp, 61, VLNote::kWantSharp, 2);
TestPitchToGrid( 1, VLNote::kWantFlat, 61, VLNote::kWantFlat, 0);
TestPitchToGrid( 1, 0, 61, VLNote::kWantFlat, -4);
TestPitchToGrid( 1, VLNote::kWantFlat, 61, VLNote::kWantFlat, -4);
TestPitchToGrid( -1, VLNote::kWant2Sharp, 61, VLNote::kWant2Sharp, 0);
TestPitchToGrid( 1, VLNote::kWantFlat, 61, VLNote::kWant2Flat, 0);
TestPitchToGrid( 1, 0, 62, 0, 0); // D, C Major
TestPitchToGrid( 1, 0, 62, VLNote::kWantNatural, 0);
TestPitchToGrid( 1, 0, 62, VLNote::kWantSharp, 0);
TestPitchToGrid( 1, 0, 62, VLNote::kWantFlat, 0);
TestPitchToGrid( 1, VLNote::kWantNatural, 62, 0, 0); // D, C Major
TestPitchToGrid( 1, VLNote::kWantNatural, 62, VLNote::kWantNatural, 0);
TestPitchToGrid( 1, VLNote::kWantNatural, 62, VLNote::kWantSharp, 0);
TestPitchToGrid( 1, VLNote::kWantNatural, 62, VLNote::kWantFlat, 0);
TestPitchToGrid( 0, VLNote::kWant2Sharp, 62, VLNote::kWant2Sharp, 0);
TestPitchToGrid( 2, VLNote::kWant2Flat, 62, VLNote::kWant2Flat, 0);
TestPitchToGrid( 6, 0, 71, 0, 0); // B, C Major
TestPitchToGrid( 6, 0, 71, VLNote::kWantNatural, 0);
TestPitchToGrid( 6, 0, 71, VLNote::kWantSharp, 0);
TestPitchToGrid( 6, VLNote::kWantNatural, 71, 0, 0); // B, C Major
TestPitchToGrid( 6, VLNote::kWantNatural, 71, VLNote::kWantNatural, 0);
TestPitchToGrid( 6, VLNote::kWantNatural, 71, VLNote::kWantSharp, 0);
TestPitchToGrid( 7, VLNote::kWantFlat, 71, VLNote::kWantFlat, 0);
TestPitchToGrid( 5, VLNote::kWant2Sharp, 71, VLNote::kWant2Sharp, 0);
TestPitchToGrid( 7, VLNote::kWantFlat, 71, VLNote::kWant2Flat, 0);
TestPitchToGrid( 7, 0, 72, 0, 0); // Octaves
TestPitchToGrid( 14, 0, 84, 0, 0);
TestPitchToGrid( -7, 0, 48, 0, 0);
TestPitchToGrid( 7, VLNote::kWantNatural, 72, 0, 0); // Octaves
TestPitchToGrid( 14, VLNote::kWantNatural, 84, 0, 0);
TestPitchToGrid( -7, VLNote::kWantNatural, 48, 0, 0);
}
#define TestGridToPitch(pitch,grid,visualIn,key) \
@ -105,12 +105,12 @@
{
VLVisualFilter filterBbMajor(-2);
TestVisualFilter(0, 0, 0, filterBbMajor);
TestVisualFilter(VLNote::kWantFlat, 0, VLNote::kWantFlat, filterBbMajor);
TestVisualFilter(VLNote::kWantNatural, 0, 0, filterBbMajor);
TestVisualFilter(VLNote::kWantSharp, 0, VLNote::kWantSharp, filterBbMajor);
TestVisualFilter(0, 2, VLNote::kWantFlat, filterBbMajor);
TestVisualFilter(VLNote::kWantSharp, 2, VLNote::kWantSharp, filterBbMajor);
TestVisualFilter(VLNote::kWantFlat, 2, 0, filterBbMajor);
TestVisualFilter(0, 0, VLNote::kWantNatural, filterBbMajor);
TestVisualFilter(VLNote::kWantFlat, 0, VLNote::kWantFlat, filterBbMajor);
TestVisualFilter(VLNote::kWantNatural, 0, VLNote::kWantNatural, filterBbMajor);
TestVisualFilter(VLNote::kWantSharp, 0, VLNote::kWantSharp, filterBbMajor);
TestVisualFilter(0, 2, VLNote::kWantFlat, filterBbMajor);
TestVisualFilter(VLNote::kWantSharp, 2, VLNote::kWantSharp, filterBbMajor);
TestVisualFilter(VLNote::kWantNatural, 2, VLNote::kWantNatural, filterBbMajor);
}
@end