VocalEasel/Sources/VLSheetViewNotes.mm

416 lines
12 KiB
Plaintext
Raw Normal View History

2006-09-11 02:49:56 +00:00
//
2007-04-27 06:41:34 +00:00
// File: VLSheetViewNotes.mm - Melody editing functionality
2006-09-11 02:49:56 +00:00
//
2007-04-27 06:41:34 +00:00
// Author(s):
//
// (MN) Matthias Neeracher
//
2011-08-26 18:19:10 +00:00
// Copyright © 2005-2011 Matthias Neeracher
2006-09-11 02:49:56 +00:00
//
#import "VLSheetView.h"
#import "VLSheetViewNotes.h"
#import "VLSheetViewInternal.h"
#import "VLDocument.h"
2006-09-11 02:49:56 +00:00
#import "VLSoundOut.h"
#import "VLPitchGrid.h"
2006-09-11 02:49:56 +00:00
#include <algorithm>
@implementation VLSheetView (Notes)
- (void) addNoteAtCursor
2006-09-11 02:49:56 +00:00
{
2011-09-11 02:39:54 +00:00
if (fCursorLocation.fMeasure != kNoMeasure && fCursorVertPos != kCursorNoPitch) {
2006-12-04 07:04:24 +00:00
[[self document] willChangeSong];
if (fCursorVisual == kCursorExtend) {
2011-09-11 02:03:22 +00:00
VLNote oldNote = [self song]->ExtendNote(fCursorLocation);
VLSoundOut::Instance()->PlayNote(oldNote);
} else if (fClickMode == 'k') {
2011-09-11 02:03:22 +00:00
[self song]->DelNote(fCursorLocation);
} else {
int pitch = VLNote::kNoPitch;
if (fClickMode == ' ')
pitch = VLGridToPitch(fCursorVertPos, fCursorVisual,
2011-09-11 02:03:22 +00:00
[self song]->Properties(fCursorLocation.fMeasure).fKey);
VLNote newNote(1, pitch, fCursorVisual & ~kCursorFlagsMask);
2011-09-11 02:03:22 +00:00
[self song]->AddNote(VLLyricsNote(newNote), fCursorLocation);
2011-09-24 20:20:43 +00:00
if (pitch != VLNote::kNoPitch)
VLSoundOut::Instance()->PlayNote(newNote);
}
fClickMode = ' ';
[[self document] didChangeSong];
2006-09-11 02:49:56 +00:00
}
}
- (void) drawLedgerLines:(int)vertPos at:(NSPoint)p
2007-04-21 22:34:55 +00:00
{
p.x += kLedgerX;
int step = (vertPos-2) / 2;
2007-04-21 22:34:55 +00:00
for (int i=0; i-- > step; ) {
NSPoint p0 = p;
p0.y += i*kLineH;
NSPoint p1 = p0;
p1.x += kLedgerW;
[NSBezierPath strokeLineFromPoint:p0 toPoint:p1];
}
for (int i=4; i++ < step; ) {
NSPoint p0 = p;
p0.y += i*kLineH;
NSPoint p1 = p0;
p1.x += kLedgerW;
[NSBezierPath strokeLineFromPoint:p0 toPoint:p1];
}
}
- (void) drawLedgerLinesInSection:(int)section withPitch:(int)pitch visual:(uint16_t)visual at:(NSPoint)p
{
[self drawLedgerLines:[self gridInSection:section withPitch:pitch visual:visual] at:p];
}
2011-09-11 02:03:22 +00:00
- (void) drawNoteCursor:(int)vertPos at:(VLLocation)at
visual:(uint16_t)visual mode:(char)mode
2006-09-11 02:49:56 +00:00
{
int cursorX;
int cursorY;
VLMusicElement cursorElt;
VLMusicElement accidental = mode ? [self accidentalForVisual:visual] : kMusicNothing;
2011-09-11 02:03:22 +00:00
cursorX = [self noteXAt:at];
if (visual == kCursorExtend) {
2011-09-11 02:03:22 +00:00
cursorY = [self noteYInMeasure:at.fMeasure withGrid:vertPos];
cursorElt = kMusicExtendCursor;
} else {
2008-05-29 18:54:30 +00:00
switch (mode) {
2007-04-22 02:59:52 +00:00
default:
2011-09-11 02:03:22 +00:00
cursorY = [self noteYInMeasure:at.fMeasure withGrid:vertPos] - kNoteY;
[self drawLedgerLines:vertPos at:NSMakePoint(cursorX,
2011-09-11 02:03:22 +00:00
[self systemY:fLayout->SystemForMeasure(at.fMeasure)])];
2007-04-22 02:59:52 +00:00
cursorElt = kMusicNoteCursor;
break;
case 'r':
2011-09-11 02:03:22 +00:00
cursorY = [self noteYInMeasure:at.fMeasure withGrid:3];
2007-04-22 02:59:52 +00:00
cursorElt = kMusicRestCursor;
break;
case 'k':
2011-09-11 02:03:22 +00:00
cursorY = [self noteYInMeasure:at.fMeasure withGrid:vertPos] - kNoteY;
2007-04-22 02:59:52 +00:00
cursorElt = kMusicKillCursor;
break;
}
}
2008-05-29 18:54:30 +00:00
NSPoint xy = NSMakePoint(cursorX-kNoteX, cursorY);
[[self musicElement:cursorElt]
2008-05-29 18:54:30 +00:00
compositeToPoint:xy
2006-09-11 02:49:56 +00:00
operation: NSCompositeSourceOver];
if (accidental) {
xy.y += kNoteY;
(int &)accidental += kMusicFlatCursor-kMusicFlat;
switch (accidental) {
2006-10-21 09:23:37 +00:00
case kMusicFlatCursor:
2008-05-29 18:54:30 +00:00
xy.x += kFlatW;
xy.y += kFlatY;
2006-10-21 09:23:37 +00:00
break;
case kMusicSharpCursor:
2008-05-29 18:54:30 +00:00
xy.x += kSharpW;
xy.y += kSharpY;
2006-10-21 09:23:37 +00:00
break;
case kMusic2FlatCursor:
xy.x += k2FlatW;
xy.y += k2FlatY;
break;
case kMusic2SharpCursor:
xy.x += k2SharpW;
xy.y += k2SharpY;
break;
2006-10-21 09:23:37 +00:00
default:
2008-05-29 18:54:30 +00:00
xy.x += kNaturalW;
xy.y += kNaturalY;
2006-10-21 09:23:37 +00:00
break;
}
[[self musicElement:accidental]
2008-05-29 18:54:30 +00:00
compositeToPoint:xy
2006-10-21 09:23:37 +00:00
operation: NSCompositeSourceOver];
}
2006-09-11 02:49:56 +00:00
}
2011-09-11 02:03:22 +00:00
- (void) drawNoteCursor:(int)vertPos at:(VLLocation)at visual:(uint16_t)visual
2008-05-29 18:54:30 +00:00
{
2011-09-11 02:03:22 +00:00
[self drawNoteCursor:vertPos at:at visual:visual mode:0];
2008-05-29 18:54:30 +00:00
}
- (void) drawNoteCursor
{
2011-09-11 02:03:22 +00:00
[self drawNoteCursor:fCursorVertPos at:fCursorLocation visual:fCursorVisual mode:fClickMode];
2008-05-29 18:54:30 +00:00
}
2007-05-21 08:18:58 +00:00
- (void) drawNote:(int)visual at:(NSPoint)p
2006-10-21 09:23:37 +00:00
accidental:(VLMusicElement)accidental tied:(BOOL)tied
2006-09-11 02:49:56 +00:00
{
NSPoint s = p;
NSPoint c = p;
p.x -= kNoteX;
p.y -= kNoteY;
s.x += kNoteX+kStemX;
s.y += kStemY;
//
// Draw note head
//
NSImage * head;
2011-08-27 22:12:32 +00:00
switch (visual & VLNote::kNoteHeadMask) {
2007-05-21 08:18:58 +00:00
case VLNote::kWhole:
2006-09-11 02:49:56 +00:00
head = [self musicElement:kMusicWholeNote];
break;
2007-05-21 08:18:58 +00:00
case VLNote::kHalf:
2006-09-11 02:49:56 +00:00
head = [self musicElement:kMusicHalfNote];
s.x -= 1.0f;
break;
default:
head = [self musicElement:kMusicNote];
s.x -= 2.0f;
break;
}
[head compositeToPoint:p
2006-10-21 09:23:37 +00:00
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 kMusic2Sharp:
at.x += k2SharpW;
at.y += k2SharpY;
break;
case kMusic2Flat:
at.x += k2FlatW;
at.y += k2FlatY;
break;
2006-10-21 09:23:37 +00:00
case kMusicNatural:
at.x += kNaturalW;
at.y += kNaturalY;
break;
default:
break;
2006-10-21 09:23:37 +00:00
}
[[self musicElement:accidental]
compositeToPoint:at operation: NSCompositeSourceOver];
}
2006-09-11 02:49:56 +00:00
//
// Draw stem
//
//
//
2007-05-21 08:18:58 +00:00
if (visual > 0) {
2006-09-11 02:49:56 +00:00
NSBezierPath * bz = [NSBezierPath bezierPath];
NSPoint s1 = NSMakePoint(s.x, s.y+kStemH);
NSImage * flag = nil;
2007-05-21 08:18:58 +00:00
switch (visual) {
case VLNote::kEighth:
2006-09-11 02:49:56 +00:00
flag = [self musicElement:kMusicEighthFlag];
break;
2007-05-21 08:18:58 +00:00
case VLNote::k16th:
2006-09-11 02:49:56 +00:00
flag = [self musicElement:kMusicSixteenthFlag];
s1.y += 5.0f;
break;
2007-05-21 08:18:58 +00:00
case VLNote::k32nd:
2006-09-11 02:49:56 +00:00
flag = [self musicElement:kMusicThirtysecondthFlag];
s1.y += 13.0f;
break;
}
[[NSColor blackColor] set];
[bz setLineWidth:2.0f];
[bz moveToPoint:s];
[bz lineToPoint:s1];
[bz stroke];
if (flag)
[flag compositeToPoint:s
operation: NSCompositePlusDarker];
}
//
// Draw tie
//
if (tied) {
NSPoint mid =
NSMakePoint(0.5f*(fLastNoteCenter.x+c.x),
0.5f*(fLastNoteCenter.y+c.y));
2006-09-11 02:49:56 +00:00
NSPoint dir = NSMakePoint(c.y-mid.y, c.x-mid.x);
float n = dir.x*dir.x+dir.y*dir.y;
float r = (kTieDepth*kTieDepth+n) / (2.0f*kTieDepth);
float l = (r-kTieDepth) / sqrtf(n);
mid.x += dir.x*l;
mid.y += dir.y*l;
float a1 = atan2(fLastNoteCenter.y-mid.y, fLastNoteCenter.x-mid.x);
2006-09-11 02:49:56 +00:00
float a2 = atan2(c.y-mid.y, c.x-mid.x);
NSBezierPath * bz = [NSBezierPath bezierPath];
[bz appendBezierPathWithArcWithCenter:mid radius:r
startAngle:a1*180.0f/M_PI endAngle:a2*180.0f/M_PI];
[bz stroke];
}
fLastNoteCenter = c;
2006-09-11 02:49:56 +00:00
}
2007-05-21 08:18:58 +00:00
- (void) drawRest:(int)visual at:(NSPoint)p
2006-09-11 02:49:56 +00:00
{
//
// Draw rest
//
NSImage * head = nil;
2007-05-21 08:18:58 +00:00
switch (visual) {
case VLNote::kWhole:
2006-09-11 02:49:56 +00:00
head = [self musicElement:kMusicWholeRest];
p.y += kWholeRestY;
break;
2007-05-21 08:18:58 +00:00
case VLNote::kHalf:
2006-09-11 02:49:56 +00:00
head = [self musicElement:kMusicHalfRest];
p.y += kHalfRestY;
break;
2007-05-21 08:18:58 +00:00
case VLNote::kQuarter:
2006-09-11 02:49:56 +00:00
head = [self musicElement:kMusicQuarterRest];
p.x -= kNoteX;
break;
2007-05-21 08:18:58 +00:00
case VLNote::kEighth:
2006-09-11 02:49:56 +00:00
head = [self musicElement:kMusicEighthRest];
p.x -= kNoteX;
break;
2007-05-21 08:18:58 +00:00
case VLNote::k16th:
2006-09-11 02:49:56 +00:00
head = [self musicElement:kMusicSixteenthRest];
p.x -= kNoteX;
break;
2007-05-21 08:18:58 +00:00
case VLNote::k32nd:
2006-09-11 02:49:56 +00:00
head = [self musicElement:kMusicThirtysecondthRest];
p.x -= kNoteX;
break;
}
[head compositeToPoint:p
operation: NSCompositeSourceOver];
}
2011-08-31 02:02:37 +00:00
- (void) drawTuplet:(uint16_t)tuplet bracketFrom:(int)startX to:(int)endX atY:(int)y
2009-03-14 23:06:13 +00:00
{
static NSDictionary * sTripletFont = nil;
if (!sTripletFont)
sTripletFont =
[[NSDictionary alloc] initWithObjectsAndKeys:
[NSFont fontWithName: @"Helvetica" size: 12],
NSFontAttributeName,
nil];
NSBezierPath * bz = [NSBezierPath bezierPath];
2011-08-31 02:02:37 +00:00
[bz moveToPoint: NSMakePoint(startX, y-kTupletH)];
2009-03-14 23:06:13 +00:00
[bz lineToPoint: NSMakePoint(startX, y)];
[bz lineToPoint: NSMakePoint(endX, y)];
2011-08-31 02:02:37 +00:00
[bz lineToPoint: NSMakePoint(endX, y-kTupletH)];
2009-03-14 23:06:13 +00:00
[bz stroke];
2011-08-31 02:02:37 +00:00
NSString * tupletText = tuplet == VLNote::kTriplet ? @"3"
: [NSString stringWithFormat:@"%d:%d", VLNote::TupletNum(tuplet), VLNote::TupletDenom(tuplet)];
[tupletText drawAtPoint: NSMakePoint((startX+endX)*0.5f, y+kTupletH)
2009-03-14 23:06:13 +00:00
withAttributes: sTripletFont];
}
- (void) drawNotesForSystem:(int)system
2006-09-11 02:49:56 +00:00
{
2007-12-23 12:45:17 +00:00
const int kFirstMeas = fLayout->FirstMeasure(system);
const VLSong * song = [self song];
2007-12-23 13:14:09 +00:00
const VLProperties & kProp = song->Properties(kFirstMeas);
2007-12-23 12:45:17 +00:00
const VLSystemLayout & kLayout = (*fLayout)[system];
2011-08-27 22:12:32 +00:00
const CGFloat kSystemY = [self systemY:system];
2009-03-14 23:06:13 +00:00
2007-12-23 12:45:17 +00:00
for (int m = 0; m<kLayout.NumMeasures(); ++m) {
VLVisualFilter filterVisuals(kProp.fKey);
2007-12-23 12:45:17 +00:00
int measIdx = m+kFirstMeas;
if (measIdx >= song->CountMeasures())
break;
2007-05-21 08:18:58 +00:00
const VLMeasure & measure = song->fMeasures[measIdx];
VLNoteList melody;
2007-12-23 12:45:17 +00:00
measure.DecomposeNotes(kProp, melody);
2011-09-11 02:03:22 +00:00
VLLocation at = {measIdx, VLFraction(0)};
2011-08-31 02:02:37 +00:00
float tupletStartX;
float tupletEndX;
CGFloat tupletY;
int inTuplet = 0;
uint16_t tuplet;
VLFraction tupletDur;
for (VLNoteList::const_iterator note = melody.begin();
note != melody.end();
++note
) {
2007-05-21 08:18:58 +00:00
BOOL tied = (note != melody.begin() || m)
&& note->fTied & VLNote::kTiedWithPrev;
int pitch = note->fPitch;
2009-03-14 23:06:13 +00:00
NSPoint pos;
2007-05-21 08:18:58 +00:00
if (pitch != VLNote::kNoPitch) {
[self drawLedgerLinesInSection:measure.fPropIdx withPitch:pitch
2008-05-29 18:54:30 +00:00
visual:note->fVisual
2011-09-11 02:03:22 +00:00
at:NSMakePoint([self noteXAt:at],
2008-05-29 18:54:30 +00:00
kSystemY)];
uint16_t visual = VLPitchAccidental(note->fPitch, note->fVisual, kProp.fKey);
pos =
2011-09-11 02:03:22 +00:00
NSMakePoint([self noteXAt:at],
kSystemY+[self noteYInSection:measure.fPropIdx
withPitch:pitch visual:&visual]);
int step = [self gridInSection:measure.fPropIdx
withPitch:pitch visual:note->fVisual];
2011-08-27 22:12:32 +00:00
[self drawNote:note->fVisual & VLNote::kNoteHeadMask
2007-05-21 08:18:58 +00:00
at: pos
accidental:[self accidentalForVisual:filterVisuals(step, visual)]
2007-05-21 08:18:58 +00:00
tied:tied];
} else {
2011-09-11 02:03:22 +00:00
pos = NSMakePoint([self noteXAt:at],
kSystemY+[self noteYInSection:measure.fPropIdx withPitch:65]);
2011-08-27 22:12:32 +00:00
[self drawRest:note->fVisual & VLNote::kNoteHeadMask at: pos];
2006-09-11 02:49:56 +00:00
}
2011-08-31 02:02:37 +00:00
if (uint16_t newTuplet = note->fVisual & VLNote::kTupletMask) {
tupletEndX = pos.x+kNoteW*0.5f;
if (inTuplet && newTuplet == tuplet) {
tupletY = std::max(tupletY, pos.y+kLineH);
} else {
if (inTuplet)
[self drawTuplet:tuplet bracketFrom:tupletStartX to:tupletEndX atY:tupletY];
tupletY = std::max(kSystemY+5.0f*kLineH, pos.y+kLineH);
tupletStartX = pos.x-kNoteW*0.5f;
tuplet = newTuplet;
tupletDur = 0;
inTuplet = 0;
2009-03-14 23:06:13 +00:00
}
2011-08-31 02:02:37 +00:00
++inTuplet;
tupletDur += note->fDuration / VLNote::TupletDenom(tuplet);
2011-09-12 14:17:21 +00:00
if (tuplet == VLNote::kTriplet
? tupletDur.IsPowerOfTwo() : inTuplet == VLNote::TupletNum(tuplet)
2011-08-31 02:02:37 +00:00
) {
//
// Tuplet adds up to power of two fraction
//
[self drawTuplet:tuplet bracketFrom:tupletStartX to:tupletEndX atY:tupletY];
inTuplet = 0;
}
} else if (inTuplet) {
2011-08-31 02:02:37 +00:00
[self drawTuplet:tuplet bracketFrom:tupletStartX to:tupletEndX atY:tupletY];
inTuplet = 0;
2009-03-14 23:06:13 +00:00
}
2011-09-11 02:03:22 +00:00
at.fAt = at.fAt+note->fDuration;
2006-09-11 02:49:56 +00:00
}
2011-08-31 02:02:37 +00:00
if (inTuplet)
[self drawTuplet:tuplet bracketFrom:tupletStartX to:tupletEndX atY:tupletY];
2009-03-14 23:06:13 +00:00
}
2011-09-11 02:03:22 +00:00
if (fCursorRegion == kRegionNote && fLayout->SystemForMeasure(fCursorLocation.fMeasure) == system)
2006-09-11 02:49:56 +00:00
[self drawNoteCursor];
}
@end