diff --git a/Filters/VLMusicXMLType.reader b/Filters/VLMusicXMLType.reader index b693aa1..33f22d9 100755 --- a/Filters/VLMusicXMLType.reader +++ b/Filters/VLMusicXMLType.reader @@ -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] diff --git a/Filters/VLMusicXMLType.writer b/Filters/VLMusicXMLType.writer index defa850..0d8db4a 100755 --- a/Filters/VLMusicXMLType.writer +++ b/Filters/VLMusicXMLType.writer @@ -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 diff --git a/Filters/vl.rb b/Filters/vl.rb index 5a13e92..d23bf1b 100644 --- a/Filters/vl.rb +++ b/Filters/vl.rb @@ -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 diff --git a/Sources/VLModel.cpp b/Sources/VLModel.cpp index 55bff8f..a4b73bd 100644 --- a/Sources/VLModel.cpp +++ b/Sources/VLModel.cpp @@ -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; } } diff --git a/Sources/VLPListDocument.mm b/Sources/VLPListDocument.mm index 812a58a..638f7cf 100644 --- a/Sources/VLPListDocument.mm +++ b/Sources/VLPListDocument.mm @@ -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 & properties) @@ -79,7 +81,8 @@ 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); @@ -137,14 +140,15 @@ void VLPlistVisitor::VisitNote(VLLyricsNote & n) [NSNumber numberWithInt:n.fLyrics[i].fKind], @"kind", 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]; diff --git a/Sources/VLPitchGrid.cpp b/Sources/VLPitchGrid.cpp index 5dae8fc..b1d12b9 100644 --- a/Sources/VLPitchGrid.cpp +++ b/Sources/VLPitchGrid.cpp @@ -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; } diff --git a/Sources/VLPitchGrid.h b/Sources/VLPitchGrid.h index e7a94d8..504d917 100644 --- a/Sources/VLPitchGrid.h +++ b/Sources/VLPitchGrid.h @@ -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]; }; \ No newline at end of file diff --git a/Sources/VLSheetViewNotes.mm b/Sources/VLSheetViewNotes.mm index e59d2af..4c8c2c3 100644 --- a/Sources/VLSheetViewNotes.mm +++ b/Sources/VLSheetViewNotes.mm @@ -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 diff --git a/Tests/TVLPitchGrid/TVLPitchGrid.mm b/Tests/TVLPitchGrid/TVLPitchGrid.mm index 793bf58..5de682b 100644 --- a/Tests/TVLPitchGrid/TVLPitchGrid.mm +++ b/Tests/TVLPitchGrid/TVLPitchGrid.mm @@ -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