Properly handle accidentals

This commit is contained in:
Matthias Neeracher 2006-10-21 09:23:37 +00:00
parent 601f15cb98
commit 707bbf2f82
3 changed files with 272 additions and 61 deletions

View File

@ -13,7 +13,8 @@
@class VLDocument;
enum VLMusicElement {
kMusicGClef,
kMusicNothing = 0,
kMusicGClef = 0,
kMusicFlat,
kMusicSharp,
kMusicNatural,
@ -30,7 +31,11 @@ enum VLMusicElement {
kMusicSixteenthFlag,
kMusicThirtysecondthFlag,
kMusicNoteCursor,
kMusicFlatCursor,
kMusicSharpCursor,
kMusicNaturalCursor,
kMusicRestCursor,
kMusicKillCursor,
kMusicElements
};
@ -64,6 +69,8 @@ enum VLRecalc {
int fCursorMeasure;
VLFract fCursorAt;
int fCursorPitch;
int fCursorActualPitch;
VLMusicElement fCursorAccidental;
IBOutlet id fFieldEditor;
}
@ -77,9 +84,10 @@ enum VLRecalc {
- (VLSong *) song;
- (NSImage *) musicElement:(VLMusicElement)elt;
- (int) stepWithPitch:(int)pitch;
- (float) systemY:(int)system;
- (float) noteYWithPitch:(int)pitch;
- (float) noteYInMeasure:(int)measure withPitch:(int)pitch;
- (float) noteYWithPitch:(int)pitch accidental:(VLMusicElement*)accidental;
- (float) noteYInMeasure:(int)measure withPitch:(int)pitch accidental:(VLMusicElement*)accidental;
- (float) noteXInMeasure:(int)measure at:(VLFraction)at;
- (void) scrollMeasureToVisible:(int)measure;

View File

@ -38,7 +38,11 @@ static NSString * sElementNames[kMusicElements] = {
@"sixteenth-flag",
@"thirtysecondth-flag",
@"notecursor",
@"restcursor"
@"flatcursor",
@"sharpcursor",
@"naturalcursor",
@"restcursor",
@"killcursor"
};
static float sSharpPos[] = {
@ -115,47 +119,65 @@ static float sFlatPos[] = {
return kSystemY+b.origin.y+b.size.height-(system+1)*kSystemH;
}
- (float) noteYWithPitch:(int)pitch
int8_t sSemi2Pitch[2][12] = {{
// C Db D Eb E F Gb G Ab A Bb B
0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6,
},{
// C C# D D# E F F# G G# A A# B
0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6,
}};
#define S kMusicSharp,
#define F kMusicFlat,
#define N kMusicNatural,
#define _ kMusicNothing,
VLMusicElement sSemi2Accidental[12][12] = {
// C DbD EbE F GbG AbA BbB
{N _ N _ N _ _ N _ N _ N}, // Gb major - 6 flats
{_ _ N _ N _ _ N _ N _ N}, // Db major - 5 flats
{_ _ N _ N _ F _ _ N _ N}, // Ab major - 4 flats
{_ F _ _ N _ F _ _ N _ N}, // Eb major - 3 flats
{_ F _ _ N _ F _ F _ _ N}, // Bb major - 2 flats
{_ F _ F _ _ F _ F _ _ N}, // F major - 1 flat
// C C#D D#E F F#G G#A A#B
{_ S _ S _ _ S _ S _ S _}, // C major
{_ S _ S _ N _ _ S _ S _}, // G major - 1 sharp
{N _ _ S _ N _ _ S _ S _}, // D major - 2 sharps
{N _ _ S _ N _ N _ _ S _}, // A major - 3 sharps
{N _ N _ _ N _ N _ _ S _}, // E major - 4 sharps
{N _ N _ _ N _ N _ N _ _}, // B major - 5 sharps
};
#undef S
#undef F
#undef N
#undef _
- (int) stepWithPitch:(int)pitch
{
int semi = pitch % 12;
int key = [self song]->fProperties.front().fKey;
bool useSharps = key >= 0;
return sSemi2Pitch[useSharps][semi];
}
- (float) noteYWithPitch:(int)pitch accidental:(VLMusicElement*)accidental
{
int semi = pitch % 12;
int octave = (pitch / 12) - 5;
bool useSharps = [self song]->fProperties.front().fKey >= 0;
int key = [self song]->fProperties.front().fKey;
float y = octave*3.5f*kLineH;
float sharp = useSharps ? 0.0f : 0.5f*kLineH;
*accidental = sSemi2Accidental[key+6][semi];
switch (semi) {
case 0: // C
return y-1.0f*kLineH;
case 1: // C# / Db
return y-1.0f*kLineH+sharp;
case 2: // D
return y-0.5f*kLineH;
case 3: // D# / Eb
return y-0.5f*kLineH+sharp;
case 4: // E
return y;
case 5: // F
return y+0.5f*kLineH;
case 6: // F# / Gb
return y+0.5f*kLineH+sharp;
case 7: // G
return y+1.0f*kLineH;
case 8: // G# / Ab
return y+1.0f*kLineH+sharp;
case 9: // A
return y+1.5f*kLineH;
case 10: // A# / Bb
return y+1.5f*kLineH+sharp;
case 11: // B
default:
return y+2.0f*kLineH;
}
return (octave*3.5f+[self stepWithPitch:pitch]*0.5f-1.0f)*kLineH;
}
- (float) noteYInMeasure:(int)measure withPitch:(int)pitch
- (float) noteYInMeasure:(int)measure withPitch:(int)pitch accidental:(VLMusicElement*)accidental
{
return [self systemY:measure/fMeasPerSystem]+[self noteYWithPitch:pitch];
return [self systemY:measure/fMeasPerSystem]
+ [self noteYWithPitch:pitch accidental:accidental];
}
- (float) noteXInMeasure:(int)measure at:(VLFraction)at
@ -420,7 +442,7 @@ static float sFlatPos[] = {
}
const float kSemiFloor = -3.0f*kLineH;
static int sSemiToPitch[] = {
static int8_t sSemiToPitch[] = {
53, // F
55, 57, // A
59, 60, // Middle C
@ -434,6 +456,97 @@ static int sSemiToPitch[] = {
86, 88 // E
};
static int8_t sFlatAcc[] = {
6, // Cb
11,
4, // Db
9,
2, // Eb
7, // Fb
12,
5, // Gb
10,
3, // Ab
8,
1, // Bb
};
static int8_t sSharpAcc[] = {
2, // C# is the 2nd sharp
9,
4, // D#
11,
6, // E#
1, // F#
8,
3, // G#
10,
5, // A#
12,
7, // B#
};
- (void) accidentalFromEvent:(NSEvent *)event
{
const VLProperties & prop = [self song]->fProperties.front();
fCursorAccidental = (VLMusicElement)0;
if (prop.fKey >= 0) {
if (prop.fKey >= sSharpAcc[fCursorPitch % 12]) { // Sharp in Key
switch ([event modifierFlags] & (NSAlternateKeyMask|NSCommandKeyMask)) {
case NSAlternateKeyMask:
fCursorAccidental = kMusicFlatCursor; // G# -> Gb
fCursorActualPitch = fCursorPitch-1;
break;
default:
case NSCommandKeyMask:
fCursorActualPitch = fCursorPitch+1;
break; // G# -> G#
case NSAlternateKeyMask|NSCommandKeyMask:
fCursorAccidental = kMusicNaturalCursor; // G# -> G
fCursorActualPitch = fCursorPitch;
break;
}
return;
}
} else {
if (prop.fKey <= -sFlatAcc[fCursorPitch % 12]) { // Flat in Key
switch ([event modifierFlags] & (NSAlternateKeyMask|NSCommandKeyMask)) {
default:
case NSAlternateKeyMask:
fCursorActualPitch = fCursorPitch-1;
break; // Gb -> Gb
case NSCommandKeyMask:
fCursorAccidental = kMusicSharpCursor; // Gb -> G#
fCursorActualPitch = fCursorPitch+1;
break;
case NSAlternateKeyMask|NSCommandKeyMask:
fCursorAccidental = kMusicNaturalCursor; // Gb -> G
fCursorActualPitch = fCursorPitch;
break;
}
return;
}
}
//
// Natural
//
switch ([event modifierFlags] & (NSAlternateKeyMask|NSCommandKeyMask)) {
case NSAlternateKeyMask:
fCursorAccidental = kMusicFlatCursor; // G -> Gb
fCursorActualPitch = fCursorPitch-1;
break;
case NSCommandKeyMask:
fCursorAccidental = kMusicSharpCursor; // G -> G#
fCursorActualPitch = fCursorPitch+1;
break;
default:
case NSAlternateKeyMask|NSCommandKeyMask:
fCursorActualPitch = fCursorPitch;
break; // G -> G
}
}
- (VLRegion) findRegionForEvent:(NSEvent *) event
{
fCursorPitch = VLNote::kNoPitch;
@ -475,9 +588,11 @@ static int sSemiToPitch[] = {
return fCursorRegion = kRegionLyrics;
}
loc.y -= kSystemY+kSemiFloor;
int semi = static_cast<int>(roundf(loc.y / (0.5f*kLineH)));
fCursorPitch = sSemiToPitch[semi];
loc.y -= kSystemY+kSemiFloor;
int semi = static_cast<int>(roundf(loc.y / (0.5f*kLineH)));
fCursorPitch = sSemiToPitch[semi];
[self accidentalFromEvent:event];
return fCursorRegion = kRegionNote;
}
@ -494,6 +609,14 @@ static int sSemiToPitch[] = {
[self setNeedsDisplay:(hadCursor || hasCursor)];
}
- (void)flagsChanged:(NSEvent *)event
{
if (fCursorPitch != VLNote::kNoPitch) {
[self accidentalFromEvent:event];
[self setNeedsDisplay:YES];
}
}
- (void) mouseEntered:(NSEvent *)event
{
[[self window] setAcceptsMouseMovedEvents:YES];

View File

@ -18,7 +18,7 @@
- (void) addNoteAtCursor
{
if (fCursorMeasure > -1) {
VLNote newNote(1, fClickMode==' ' ? fCursorPitch : VLNote::kNoPitch);
VLNote newNote(1, fClickMode==' ' ? fCursorActualPitch : VLNote::kNoPitch);
if (fClickMode == 'k')
[self song]->DelNote(fCursorMeasure, fCursorAt);
@ -38,9 +38,10 @@
- (void) startKeyboardCursor
{
if (fCursorMeasure < 0) {
fCursorMeasure = 0;
fCursorPitch = VLNote::kMiddleC;
fCursorAt = VLFraction(0);
fCursorMeasure = 0;
fCursorPitch = VLNote::kMiddleC;
fCursorActualPitch = fCursorPitch;
fCursorAt = VLFraction(0);
}
}
@ -48,24 +49,61 @@
{
int cursorX;
int cursorY;
VLMusicElement accidental;
VLMusicElement cursorElt;
cursorX = [self noteXInMeasure:fCursorMeasure at:fCursorAt]-kNoteX;
if (fClickMode == ' ') {
switch (fClickMode) {
default:
cursorY =
[self noteYInMeasure:fCursorMeasure withPitch:fCursorPitch]-kNoteY;
[self noteYInMeasure:fCursorMeasure
withPitch:fCursorPitch
accidental:&accidental]
-kNoteY;
cursorElt = kMusicNoteCursor;
} else {
cursorY = [self noteYInMeasure:fCursorMeasure withPitch:65];
break;
case 'r':
cursorY = [self noteYInMeasure:fCursorMeasure
withPitch:65
accidental:&accidental];
cursorElt = kMusicRestCursor;
break;
case 'k':
cursorY = [self noteYInMeasure:fCursorMeasure
withPitch:fCursorPitch
accidental:&accidental];
cursorElt = kMusicKillCursor;
break;
}
NSPoint at = NSMakePoint(cursorX, cursorY);
[[self musicElement:cursorElt]
compositeToPoint:NSMakePoint(cursorX, cursorY)
compositeToPoint:at
operation: NSCompositeSourceOver];
if (fCursorAccidental) {
at.y += kNoteY;
switch (cursorElt= fCursorAccidental) {
case kMusicFlatCursor:
at.x += kFlatW;
at.y += kFlatY;
break;
case kMusicSharpCursor:
at.x += kSharpW;
at.y += kSharpY;
break;
default:
at.x += kNaturalW;
at.y += kNaturalY;
break;
}
[[self musicElement:cursorElt]
compositeToPoint:at
operation: NSCompositeSourceOver];
}
}
- (void) drawNote:(VLFraction)dur at:(NSPoint)p tied:(BOOL)tied
- (void) drawNote:(VLFraction)dur at:(NSPoint)p
accidental:(VLMusicElement)accidental tied:(BOOL)tied
{
NSPoint s = p;
NSPoint c = p;
@ -93,6 +131,29 @@
[head compositeToPoint:p
operation: NSCompositePlusDarker];
//
// Draw accidental
//
if (accidental) {
NSPoint at = p;
at.y += kNoteY;
switch (accidental) {
case kMusicSharp:
at.x += kSharpW;
at.y += kSharpY;
break;
case kMusicFlat:
at.x += kFlatW;
at.y += kFlatY;
break;
case kMusicNatural:
at.x += kNaturalW;
at.y += kNaturalY;
break;
}
[[self musicElement:accidental]
compositeToPoint:at operation: NSCompositeSourceOver];
}
//
// Draw stem
//
//
@ -192,6 +253,8 @@
float kSystemY = [self systemY:system];
for (int m = 0; m<fMeasPerSystem; ++m) {
VLMusicElement accidentals[7];
memset(accidentals, 0, 7*sizeof(VLMusicElement));
int measIdx = m+system*fMeasPerSystem;
if (measIdx >= song->CountMeasures())
break;
@ -229,17 +292,34 @@
} else {
noteDur = partialDur;
}
if (pitch != VLNote::kNoPitch)
if (pitch != VLNote::kNoPitch) {
VLMusicElement accidental;
NSPoint pos =
NSMakePoint([self noteXInMeasure:m at:at],
kSystemY+[self noteYWithPitch:pitch
accidental:&accidental]);
VLMusicElement acc = accidental;
int step= [self stepWithPitch:pitch];
if (acc == accidentals[step])
acc = kMusicNothing; // Don't repeat accidentals
else if (acc == kMusicNothing)
if (accidentals[step] == kMusicNatural) // Resume signature
acc = prop.fKey < 0 ? kMusicFlat : kMusicSharp;
else
acc = kMusicNatural;
[self drawNote:noteDur
at: NSMakePoint(
[self noteXInMeasure:m at:at],
kSystemY+[self noteYWithPitch:pitch])
at: pos
accidental: acc
tied:!first];
else
[self drawRest:noteDur
at: NSMakePoint(
[self noteXInMeasure:m at:at],
kSystemY+[self noteYWithPitch:65])];
accidentals[step] = accidental;
} else {
VLMusicElement accidental;
NSPoint pos =
NSMakePoint([self noteXInMeasure:m at:at],
kSystemY+[self noteYWithPitch:65
accidental:&accidental]);
[self drawRest:noteDur at: pos];
}
dur -= partialDur;
at += partialDur;
first = NO;