VocalEasel/Sources/VLSheetViewNotes.mm

332 lines
7.9 KiB
Plaintext
Raw Normal View History

2006-09-11 02:49:56 +00:00
//
// VLSheetViewNotes.mm
// Vocalese
//
// Created by Matthias Neeracher on 1/4/06.
// Copyright 2006 __MyCompanyName__. All rights reserved.
//
#import "VLSheetView.h"
#import "VLSheetViewNotes.h"
#import "VLSheetViewInternal.h"
#import "VLSoundOut.h"
#include <algorithm>
@implementation VLSheetView (Notes)
static int sSemiToPitch[] = {
53, // F
55, 57, // A
59, 60, // Middle C
62, 64, // E
65, 67, // G
69, 71, // B
72, 74, // D
76, 77, // F
79, 81, // A
83, 84, // C
86, 88 // E
};
- (void) mouseMoved:(NSEvent *)event
{
if ([event modifierFlags] & NSAlphaShiftKeyMask)
return; // Keyboard mode, ignore mouse
const VLProperties & prop = [self song]->fProperties.front();
NSPoint loc = [event locationInWindow];
loc = [self convertPoint:loc fromView:nil];
loc.x -= fNoteRect.origin.x;
int measure = static_cast<int>(loc.x / fMeasureW);
loc.x -= measure*fMeasureW;
int group = static_cast<int>(loc.x / ((fDivPerGroup+1)*kNoteW));
loc.x -= group*(fDivPerGroup+1)*kNoteW;
2006-09-11 02:49:56 +00:00
int div = static_cast<int>(roundf(loc.x / kNoteW))-1;
div = std::min(std::max(div, 0), fDivPerGroup-1);
VLFraction at(div+group*fDivPerGroup, 4*prop.fDivisions);
2006-09-11 02:49:56 +00:00
loc.y -= fNoteRect.origin.y;
2006-09-11 02:49:56 +00:00
int semi = static_cast<int>(roundf(loc.y / (0.5f*kLineH)));
int pitch = sSemiToPitch[semi];
[self setNoteCursorMeasure:measure at:at pitch:pitch];
}
- (void) addNoteAtCursor:(BOOL)isRest
{
if (fNoteCursorMeasure > -1) {
VLNote newNote(1, !isRest ? fNoteCursorPitch : VLNote::kNoPitch);
2006-09-11 02:49:56 +00:00
[self song]->AddNote(newNote, fNoteCursorMeasure, fNoteCursorAt);
2006-09-11 02:49:56 +00:00
[self setNeedsDisplay:YES];
VLSoundOut::Instance()->PlayNote(newNote);
}
}
- (void) mouseDown:(NSEvent *)event
{
[self mouseMoved:event];
[self addNoteAtCursor: ([event modifierFlags] & NSShiftKeyMask) != 0];
}
- (void) mouseEntered:(NSEvent *)event
{
[[self window] setAcceptsMouseMovedEvents:YES];
[self mouseMoved:event];
}
- (void) mouseExited:(NSEvent *)event
{
[[self window] setAcceptsMouseMovedEvents:NO];
if (!([event modifierFlags] & NSAlphaShiftKeyMask))
[self hideNoteCursor];
}
- (void) startKeyboardCursor
{
if (fNoteCursorMeasure < 0) {
fNoteCursorMeasure = 0;
fNoteCursorPitch = VLNote::kMiddleC;
fNoteCursorAt = VLFraction(0);
2006-09-11 02:49:56 +00:00
}
}
- (void) keyDown:(NSEvent *)event
{
NSString * k = [event charactersIgnoringModifiers];
switch ([k characterAtIndex:0]) {
case '\r':
[self startKeyboardCursor];
[self addNoteAtCursor];
break;
case ' ':
[self startKeyboardCursor];
VLSoundOut::Instance()->PlayNote(VLNote(1, fNoteCursorPitch));
2006-09-11 02:49:56 +00:00
break;
}
}
- (void) hideNoteCursor
{
fNoteCursorMeasure = -1;
2006-09-11 02:49:56 +00:00
[self setNeedsDisplay:YES];
}
- (void) drawNoteCursor
{
NSPoint note =
NSMakePoint([self noteXInMeasure:fNoteCursorMeasure at:fNoteCursorAt],
[self noteYWithPitch:fNoteCursorPitch]);
2006-09-11 02:49:56 +00:00
NSRect noteCursorRect =
NSMakeRect(note.x-kNoteX, note.y-kNoteY, 2.0f*kNoteX, 2.0f*kNoteY);
[[self musicElement:kMusicNoteCursor]
compositeToPoint:NSMakePoint(note.x-kNoteX, note.y-kNoteY)
operation: NSCompositeSourceOver];
}
- (void) drawNote:(VLFraction)dur at:(NSPoint)p tied:(BOOL)tied
{
NSPoint s = p;
NSPoint c = p;
p.x -= kNoteX;
p.y -= kNoteY;
s.x += kNoteX+kStemX;
s.y += kStemY;
//
// Draw note head
//
NSImage * head;
switch (dur.fDenom) {
case 1:
head = [self musicElement:kMusicWholeNote];
break;
case 2:
head = [self musicElement:kMusicHalfNote];
s.x -= 1.0f;
break;
default:
head = [self musicElement:kMusicNote];
s.x -= 2.0f;
break;
}
[head compositeToPoint:p
operation: NSCompositePlusDarker];
//
// Draw stem
//
//
//
if (dur.fDenom > 1) {
NSBezierPath * bz = [NSBezierPath bezierPath];
NSPoint s1 = NSMakePoint(s.x, s.y+kStemH);
NSImage * flag = nil;
switch (dur.fDenom) {
case 8:
flag = [self musicElement:kMusicEighthFlag];
break;
case 16:
flag = [self musicElement:kMusicSixteenthFlag];
s1.y += 5.0f;
break;
case 32:
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
}
- (void) drawRest:(VLFraction)dur at:(NSPoint)p
{
//
// Draw rest
//
NSImage * head = nil;
switch (dur.fDenom) {
case 1:
head = [self musicElement:kMusicWholeRest];
p.y += kWholeRestY;
break;
case 2:
head = [self musicElement:kMusicHalfRest];
p.y += kHalfRestY;
break;
case 4:
head = [self musicElement:kMusicQuarterRest];
p.x -= kNoteX;
break;
case 8:
head = [self musicElement:kMusicEighthRest];
p.x -= kNoteX;
break;
case 16:
head = [self musicElement:kMusicSixteenthRest];
p.x -= kNoteX;
break;
case 32:
head = [self musicElement:kMusicThirtysecondthRest];
p.x -= kNoteX;
break;
}
[head compositeToPoint:p
operation: NSCompositeSourceOver];
}
- (void) drawNotes
{
const VLSong * song = [self song];
const VLProperties & prop = song->fProperties.front();
BOOL swing= !(prop.fDivisions % 3); // In swing mode?
VLFraction swung(3, prop.fDivisions*8, true); // Which notes to swing
VLFraction swingGrid(2*swung); // Alignment of swing notes
for (int system = 0; system<fNumSystems; ++system) {
2006-10-02 05:29:37 +00:00
float kLineY = [self systemY:system];
for (int m = 0; m<fMeasPerSystem; ++m) {
int measIdx = m+system*fMeasPerSystem;
2006-10-02 05:29:37 +00:00
if (measIdx >= song->CountMeasures())
break;
const VLMeasure measure = song->fMeasures[measIdx];
const VLNoteList & melody = measure.fMelody;
VLFraction at(0);
for (VLNoteList::const_iterator note = melody.begin();
note != melody.end();
++note
) {
VLFraction dur = note->fDuration;
BOOL first = !m || !note->fTied;
int pitch = note->fPitch;
while (dur > 0) {
VLFraction partialDur; // Actual value of note drawn
measure.fProperties->PartialNote(at, dur, &partialDur);
2006-09-11 02:49:56 +00:00
2006-10-02 05:29:37 +00:00
BOOL triplet = !(partialDur.fDenom % 3);
VLFraction noteDur(1); // Visual value of note
2006-09-11 02:49:56 +00:00
2006-10-02 05:29:37 +00:00
if (triplet) {
if (swing) { // Swing 8ths / 16ths are written as straight 8ths
if (partialDur == 4*swung/3 && (at % swingGrid) == 0) {
noteDur = swung;
triplet = NO;
} else if (partialDur == 2*swung/3 && ((at+partialDur) % swingGrid) == 0) {
noteDur = swung;
triplet = NO;
} else {
noteDur = 4*partialDur/3;
}
2006-09-11 02:49:56 +00:00
} else {
noteDur = 4*partialDur/3;
}
} else {
2006-10-02 05:29:37 +00:00
noteDur = partialDur;
2006-09-11 02:49:56 +00:00
}
2006-10-02 05:29:37 +00:00
if (pitch != VLNote::kNoPitch)
[self drawNote:noteDur
at: NSMakePoint(
[self noteXInMeasure:m at:at],
kLineY+[self noteYWithPitch:pitch])
tied:!first];
else
[self drawRest:noteDur
at: NSMakePoint(
[self noteXInMeasure:m at:at],
kLineY+[self noteYWithPitch:65])];
dur -= partialDur;
at += partialDur;
first = NO;
2006-09-11 02:49:56 +00:00
}
}
}
}
if (fNoteCursorMeasure > -1)
2006-09-11 02:49:56 +00:00
[self drawNoteCursor];
}
- (void) setNoteCursorMeasure:(int)measure at:(VLFraction)at pitch:(int)pitch
{
if (measure != fNoteCursorMeasure || at != fNoteCursorAt
|| pitch != fNoteCursorPitch
2006-09-11 02:49:56 +00:00
) {
fNoteCursorMeasure = measure;
fNoteCursorAt = at;
fNoteCursorPitch = pitch;
2006-09-11 02:49:56 +00:00
[self setNeedsDisplay:YES];
}
}
@end