mirror of
https://github.com/microtherion/VocalEasel.git
synced 2024-12-22 11:14:00 +00:00
Properly handle accidentals
This commit is contained in:
parent
601f15cb98
commit
707bbf2f82
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
@ -91,7 +129,30 @@
|
|||
break;
|
||||
}
|
||||
[head compositeToPoint:p
|
||||
operation: NSCompositePlusDarker];
|
||||
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;
|
||||
|
|
Loading…
Reference in New Issue
Block a user