Rev. | 25 |
---|---|
大小 | 39,091 字节 |
时间 | 2012-06-19 00:48:59 |
作者 | toshinagata1964 |
Log Message | Selection mode of the piano roll window is being reworked. (May be still imcomplete) |
//
// StripChartView.m
// Created by Toshi Nagata on Sun Jan 26 2003.
//
/*
Copyright (c) 2003-2012 Toshi Nagata. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#import "StripChartView.h"
#import <math.h>
#import "GraphicWindowController.h"
#import "MyDocument.h"
#import "MyMIDISequence.h"
#import "MDObjects.h"
#import "NSEventAdditions.h"
#import "NSCursorAdditions.h"
/* The constants to approximate a parabola with a cubic bezier curve */
/* The cubic bezier curve (0, 0)-(ALPHA, 0)-(1-BETA, 1-2*BETA)-(1,1) approximates
a parabola y = x^2 in [0, 1]. */
/* #define ALPHA 0.377009
#define BETA 0.286601 */
static const float sParabolaPoints[] = {0, 0, 0.35, 0, 0.7, 0.4, 1, 1, -1};
static const float sArcPoints[] = {0, 0, 0.15, 0.6, 0.33, 1, 0.5, 1, 0.67, 1, 0.85, 0.6, 1, 0, -1};
static const float sSigmoidPoints[] = {0, 0, 0.45, 0, 0.55, 1, 1, 1, -1};
/* The resolutions for pencil drawing */
/* New events are generated so that the time/tick/value intervals are no less than
these values. */
static MDTimeType sTimeResolution = 100;
static MDTickType sTickResolution = 1;
static float sValueResolution = 1.0;
@implementation StripChartView
- (int)clientViewType
{
return kGraphicStripChartViewType;
}
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
eventKind = 0; /* Undefined */
eventCode = 0;
minValue = 0.0;
maxValue = 128.0;
calib = NULL;
}
return self;
}
- (void)dealloc
{
if (calib != NULL)
MDCalibratorRelease(calib);
[super dealloc];
}
- (BOOL)hasVerticalScroller
{
return NO;
}
static float
getYValue(const MDEvent *ep, int eventKind)
{
if (eventKind == kMDEventNote)
return MDGetNoteOnVelocity(ep);
else if (eventKind == kMDEventInternalNoteOff)
return MDGetNoteOffVelocity(ep);
else if (eventKind == kMDEventTempo)
return MDGetTempo(ep);
else return MDGetData1(ep);
}
- (void)drawVelocityInRect: (NSRect)aRect
{
float ppt, height;
MDTickType beginTick, endTick;
long n;
float dx, dy;
NSBezierPath *draggingPath;
NSMutableArray *array;
height = [self bounds].size.height;
ppt = [dataSource pixelsPerTick];
beginTick = (MDTickType)floor(aRect.origin.x / ppt);
endTick = (MDTickType)ceil((aRect.origin.x + aRect.size.width) / ppt);
draggingPath = nil;
array = nil;
if (stripDraggingMode > 0) {
dx = draggingPoint.x - draggingStartPoint.x;
dy = draggingPoint.y - draggingStartPoint.y;
if (dx < -0.5 || dx > 0.5 || dy < -0.5 || dy > 0.5) {
array = [NSMutableArray array];
}
beginTick = (MDTickType)floor((aRect.origin.x - (dx > 0 ? dx : -dx)) / ppt);
endTick = (MDTickType)ceil((aRect.origin.x + aRect.size.width + (dx > 0 ? dx : -dx)) / ppt);
}
for (n = [dataSource visibleTrackCount] - 1; n >= 0; n--) {
float x, y, ybase;
MDEvent *ep;
MDPointer *pt;
NSColor *color;
MDTrack *track;
long trackNo;
trackNo = [dataSource sortedTrackNumberAtIndex: n];
track = [[[dataSource document] myMIDISequence] getTrackAtIndex: trackNo];
if (track == NULL)
continue;
color = [[dataSource document] colorForTrack: trackNo enabled: [dataSource isFocusTrack: trackNo]];
[color set];
pt = MDPointerNew(track);
if (pt == NULL)
break;
MDPointerJumpToTick(pt, beginTick);
MDPointerBackward(pt);
ybase = aRect.origin.y - 1;
while ((ep = MDPointerForward(pt)) != NULL && MDGetTick(ep) < endTick) {
if (MDGetKind(ep) != kMDEventNote)
continue;
y = ceil((getYValue(ep, eventKind) - minValue) / (maxValue - minValue) * height) - 0.5;
x = floor(MDGetTick(ep) * ppt) + 0.5;
if (y >= aRect.origin.y) {
if ([[dataSource document] isSelectedAtPosition: MDPointerGetPosition(pt) inTrack: trackNo]) {
NSFrameRect(NSMakeRect(x - 1, y - 1, 3, 3));
if (array != nil) {
if (draggingPath == nil)
draggingPath = [NSBezierPath bezierPath];
[draggingPath appendBezierPathWithRect: NSMakeRect(x + dx - 1, y + dy - 1, 3, 3)];
[draggingPath moveToPoint: NSMakePoint(x + dx, y + dy)];
[draggingPath lineToPoint: NSMakePoint(x + dx, ybase)];
}
y -= 1.0;
}
[NSBezierPath strokeLineFromPoint: NSMakePoint(x, y) toPoint: NSMakePoint(x, ybase)];
}
}
if (array != nil && draggingPath != nil) {
[array addObject:[color colorWithAlphaComponent: 0.5]];
[array addObject:draggingPath];
draggingPath = nil;
}
MDPointerRelease(pt);
}
if (array != nil) {
int i = 0;
n = [array count];
while (i < n) {
// NSLog(@"color: %@ path (%d)", [array objectAtIndex: i], [[array objectAtIndex: i + 1] elementCount]);
[[array objectAtIndex: i++] set];
[[array objectAtIndex: i++] stroke];
}
}
}
- (void)drawBoxStripInRect: (NSRect)aRect
{
float ppt;
long n;
MDTickType beginTick, endTick;
float height;
float dx, dy;
NSBezierPath *draggingPath;
NSMutableArray *array;
if (calib == NULL)
return;
height = [self bounds].size.height;
ppt = [dataSource pixelsPerTick];
draggingPath = nil;
array = nil;
if (stripDraggingMode > 0) {
dx = draggingPoint.x - draggingStartPoint.x;
dy = draggingPoint.y - draggingStartPoint.y;
if (dx < -0.5 || dx > 0.5 || dy < -0.5 || dy > 0.5) {
array = [NSMutableArray array];
aRect.origin.x -= (dx > 0 ? dx : -dx);
aRect.size.width += (dx > 0 ? dx : -dx) * 2;
}
}
beginTick = (MDTickType)floor(aRect.origin.x / ppt);
endTick = (MDTickType)ceil((aRect.origin.x + aRect.size.width) / ppt);
MDCalibratorJumpToTick(calib, beginTick);
if (eventKind == kMDEventTempo)
n = 0;
else
n = [dataSource visibleTrackCount] - 1;
for ( ; n >= 0; n--) {
float x, y, xlast, ylast;
MDEvent *ep;
MDPointer *pt;
NSRect rect;
NSColor *color, *shadowColor;
MDTrack *track;
long trackNo, poslast;
BOOL isFocused;
if (eventKind == kMDEventTempo)
trackNo = 0;
else
trackNo = [dataSource sortedTrackNumberAtIndex: n];
track = [[[dataSource document] myMIDISequence] getTrackAtIndex: trackNo];
isFocused = [dataSource isFocusTrack: trackNo];
color = [[dataSource document] colorForTrack: trackNo enabled: isFocused];
shadowColor = (isFocused ? [color shadowWithLevel: 0.1] : color);
pt = MDCalibratorCopyPointer(calib, track, eventKind, eventCode);
if (pt == NULL)
continue;
ep = MDPointerCurrent(pt);
if (ep == NULL) {
xlast = ylast = 0;
poslast = -1;
} else {
ylast = ceil((getYValue(ep, eventKind) - minValue) / (maxValue - minValue) * height);
xlast = floor(MDGetTick(ep) * ppt);
poslast = MDPointerGetPosition(pt);
}
while (1) {
ep = MDPointerForward(pt);
if (ep != NULL) {
if (MDGetKind(ep) != eventKind)
continue;
if (eventCode != -1 && MDGetCode(ep) != eventCode)
continue;
y = ceil((getYValue(ep, eventKind) - minValue) / (maxValue - minValue) * height);
x = floor(MDGetTick(ep) * ppt);
} else {
x = [self bounds].size.width;
}
rect = NSMakeRect(xlast, -1, x - xlast + 1, ylast + 1);
if (NSIntersectsRect(rect, aRect)) {
if ([[dataSource document] isSelectedAtPosition: poslast inTrack: trackNo]) {
[color set];
NSFrameRect(NSMakeRect(xlast - 1, ylast - 2, 3, 3));
if (array != nil) {
if (draggingPath == nil)
draggingPath = [NSBezierPath bezierPath];
[draggingPath appendBezierPathWithRect: NSMakeRect(xlast + dx - 1, ylast + dy - 2, 3, 3)];
[draggingPath moveToPoint: NSMakePoint(xlast + dx, ylast + dy)];
[draggingPath lineToPoint: NSMakePoint(xlast + dx, 0)];
}
[[NSColor whiteColor] set];
} else {
[shadowColor set];
}
[NSBezierPath fillRect: rect]; // NSRectFill(rect);
[color set];
[NSBezierPath strokeRect: rect]; // NSFrameRect(rect);
}
if (ep == NULL || xlast >= aRect.origin.x + aRect.size.width)
break;
xlast = x;
ylast = y;
poslast = MDPointerGetPosition(pt);
}
if (draggingPath != nil) {
[array addObject:[color colorWithAlphaComponent: 0.5]];
[array addObject:draggingPath];
draggingPath = nil;
}
MDPointerRelease(pt);
}
if (array != nil) {
int i = 0;
n = [array count];
while (i < n) {
[[array objectAtIndex: i++] set];
[[array objectAtIndex: i++] stroke];
}
}
}
- (void)drawRect: (NSRect)aRect
{
NSPoint pt;
NSRect bounds;
NSEraseRect(aRect);
[self paintEditingRange: aRect startX: NULL endX: NULL];
if (eventKind == kMDEventNote || eventKind == kMDEventInternalNoteOff)
[self drawVelocityInRect: aRect];
else
[self drawBoxStripInRect: aRect];
/* Draw a line at the vertical center */
bounds = [self bounds];
pt.x = bounds.origin.x;
pt.y = floor(bounds.origin.x + bounds.size.height / 2) + 0.5;
[[NSColor lightGrayColor] set];
[NSBezierPath strokeLineFromPoint: pt toPoint: NSMakePoint(pt.x + bounds.size.width, pt.y)];
if ([self isDragging])
[self drawSelectRegion];
}
- (void)setKindAndCode: (long)kindAndCode
{
int newKind, newCode;
MDSequence *sequence;
float minval, maxval;
newKind = (kindAndCode >> 16) & 65535;
newCode = kindAndCode & 65535;
if ((newKind == 65535 || newKind == eventKind) && (newCode == 65535 || newCode == eventCode))
return; /* Do nothing */
if (newKind != 65535) {
eventKind = newKind;
if (eventKind == kMDEventTempo) {
minval = 0.0;
maxval = 511.0;
} else if (eventKind == kMDEventPitchBend) {
minval = -8192.0;
maxval = 8191.0;
} else {
minval = 0.0;
maxval = 127.0;
}
[self setMinValue: minval];
[self setMaxValue: maxval];
if (eventKind == kMDEventNote || eventKind == kMDEventInternalNoteOff)
mode = kStripChartBarMode;
else
mode = kStripChartBoxMode;
[self setYScale: [[self superview] bounds].size.height / (maxval - minval)];
}
if (newCode != 65535)
eventCode = newCode;
else eventCode = -1;
if (calib != NULL)
MDCalibratorRelease(calib);
calib = NULL;
sequence = [[[dataSource document] myMIDISequence] mySequence];
calib = MDCalibratorNew(sequence, NULL, kMDEventTempo, -1);
// if (eventKind == kMDEventTempo) {
// calib = MDCalibratorNew(sequence, NULL, eventKind, -1);
// } else if (eventKind != kMDEventNote && eventKind != kMDEventInternalNoteOff) {
if (eventKind != kMDEventTempo && eventKind != kMDEventNote && eventKind != kMDEventInternalNoteOff) {
int i;
MDTrack *track;
for (i = [dataSource visibleTrackCount] - 1; i >= 0; i--) {
track = MDSequenceGetTrack(sequence, [dataSource sortedTrackNumberAtIndex: i]);
if (track != NULL) {
if (calib == NULL)
calib = MDCalibratorNew(sequence, track, eventKind, eventCode);
else
MDCalibratorAppend(calib, track, eventKind, eventCode);
}
}
}
[self reloadData];
[self setNeedsDisplay: YES];
}
- (void)addTrack: (int)track
{
if (calib != NULL && eventKind != kMDEventTempo && eventKind != kMDEventNote && eventKind != kMDEventInternalNoteOff) {
MDTrack *aTrack = [[[dataSource document] myMIDISequence] getTrackAtIndex: track];
if (aTrack != NULL)
MDCalibratorAppend(calib, aTrack, eventKind, eventCode);
/* { // debug
MDEventKind kind;
short code;
int n = 0;
MDTrack *infoTrack;
fprintf(stderr, "%s[%d]: ", __FILE__, __LINE__);
while (MDCalibratorGetInfo(calib, n++, &infoTrack, &kind, &code) == kMDNoError) {
fprintf(stderr, "track %p kind %d code %d; ", infoTrack, (int)kind, (int)code);
}
fprintf(stderr, "\n");
} */
}
}
- (void)removeTrack: (int)track
{
if (calib != NULL && eventKind != kMDEventTempo && eventKind != kMDEventNote && eventKind != kMDEventInternalNoteOff) {
MDTrack *infoTrack;
MDTrack *aTrack = [[[dataSource document] myMIDISequence] getTrackAtIndex: track];
if (aTrack != NULL) {
int n = 0;
while (MDCalibratorGetInfo(calib, n, &infoTrack, NULL, NULL) == kMDNoError) {
if (infoTrack == aTrack)
MDCalibratorRemoveAtIndex(calib, n);
else n++;
}
/* { // debug
MDEventKind kind;
short code;
n = 0;
fprintf(stderr, "%s[%d]: ", __FILE__, __LINE__);
while (MDCalibratorGetInfo(calib, n++, &infoTrack, &kind, &code) == kMDNoError) {
fprintf(stderr, "track %p kind %d code %d; ", infoTrack, (int)kind, (int)code);
}
fprintf(stderr, "\n");
} */
}
}
}
- (long)kindAndCode
{
return ((((long)eventKind) & 65535) << 16) | (eventCode & 65535);
}
- (void)invalidateDraggingRegion
{
NSRect rect = selectionRect;
rect.origin.x += draggingPoint.x - draggingStartPoint.x;
if (draggingPoint.y > draggingStartPoint.y)
rect.size.height += draggingPoint.y - draggingStartPoint.y;
rect = NSInsetRect(rect, -2, -2);
dprintf(2, "invalidateDraggingRegion: (%g %g %g %g)\n", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
[self setNeedsDisplayInRect: rect];
}
- (NSRect)boundRectForSelection
{
int i, n;
MDTickType tick, minTick, maxTick;
float ppt;
// float minY;
float maxY;
float height = [self bounds].size.height;
MyDocument *document = [dataSource document];
minTick = kMDMaxTick;
maxTick = kMDNegativeTick;
// minY = 10000000.0;
maxY = -10000000.0;
ppt = [dataSource pixelsPerTick];
for (i = 0; (n = [dataSource sortedTrackNumberAtIndex: i]) >= 0; i++) {
long index;
MDPointer *pt;
MDEvent *ep;
float y;
MDTrack *track = [[document myMIDISequence] getTrackAtIndex: n];
MDPointSet *pset = [[document selectionOfTrack: n] pointSet];
if (track == NULL || pset == NULL)
continue;
pt = MDPointerNew(track);
if (pt == NULL)
break;
MDPointerSetPositionWithPointSet(pt, pset, -1, &index);
while ((ep = MDPointerForwardWithPointSet(pt, pset, &index)) != NULL) {
if (MDGetKind(ep) != eventKind || (eventCode != -1 && MDGetCode(ep) != eventCode))
continue;
tick = MDGetTick(ep);
y = getYValue(ep, eventKind);
// if (y < minY)
// minY = y;
if (y > maxY)
maxY = y;
if (tick < minTick)
minTick = tick;
if (tick > maxTick)
maxTick = tick;
}
}
if (minTick > maxTick || maxY < 0) {
return NSMakeRect(0, 0, 0, 0);
} else {
// minY = (minY - minValue) / (maxValue - minValue) * height;
maxY = (maxY - minValue) / (maxValue - minValue) * height;
return NSMakeRect(minTick * ppt - 1, 0, (maxTick - minTick) * ppt + 3, maxY + 1);
}
}
/* Returns 0-3; 0: no event, 1: the hot spot, 2: on the vertical line, 3: on the horizontal line (box mode only) */
- (int)findStripUnderPoint: (NSPoint)aPoint track: (int *)outTrack position: (long *)outPosition mdEvent: (MDEvent **)outEvent
{
int num, i, retval;
int trackNum;
long poslast;
MDEvent *ep;
float ppt = [dataSource pixelsPerTick];
float x, y, xlast, ylast;
float height = [self bounds].size.height;
MyDocument *document = (MyDocument *)[dataSource document];
MDTickType theTick;
num = [dataSource visibleTrackCount];
theTick = (MDTickType)((aPoint.x - 1) / ppt);
if (calib != NULL)
MDCalibratorJumpToTick(calib, theTick);
retval = 0;
for (i = 0; i < num; i++) {
MDTrack *track;
MDPointer *pt;
trackNum = [dataSource sortedTrackNumberAtIndex: i];
track = [[document myMIDISequence] getTrackAtIndex: trackNum];
if (track == NULL)
continue;
if (eventKind != kMDEventNote && eventKind != kMDEventInternalNoteOff)
pt = MDCalibratorCopyPointer(calib, track, eventKind, eventCode);
else {
pt = MDPointerNew(track);
if (pt != NULL) {
MDPointerJumpToTick(pt, theTick);
MDPointerBackward(pt);
}
}
if (pt == NULL)
continue;
ep = MDPointerCurrent(pt);
if (ep == NULL) {
xlast = ylast = 0;
poslast = -1;
} else {
ylast = ceil((getYValue(ep, eventKind) - minValue) / (maxValue - minValue) * height);
xlast = floor(MDGetTick(ep) * ppt);
poslast = MDPointerGetPosition(pt);
}
while (retval == 0) {
ep = MDPointerForward(pt);
if (ep != NULL) {
if (MDGetKind(ep) != eventKind)
continue;
if (eventCode != -1 && MDGetCode(ep) != eventCode)
continue;
y = ceil((getYValue(ep, eventKind) - minValue) / (maxValue - minValue) * height);
x = floor(MDGetTick(ep) * ppt);
} else {
x = [self bounds].size.width + 2;
}
if (aPoint.x >= x - 1 && aPoint.x <= x + 1) {
if (aPoint.y >= y - 1 && aPoint.y <= y + 1)
retval = 1;
else if (aPoint.y <= y + 1 || (poslast >= 0 && aPoint.y <= ylast + 1))
retval = 2;
poslast = MDPointerGetPosition(pt);
break; /* found */
}
if (aPoint.x < x - 1) {
if (eventKind != kMDEventNote && eventKind != kMDEventInternalNoteOff) {
/* horizontal line: box mode */
if (poslast >= 0) {
if (aPoint.y >= ylast - 1 && aPoint.y <= ylast + 1) {
MDPointerSetPosition(pt, poslast);
ep = MDPointerCurrent(pt);
retval = 3;
} else if (aPoint.y <= ylast) {
/* Stop searching, as the box hides events in the following tracks */
retval = -1;
}
}
}
break;
}
if (ep == NULL)
break;
}
MDPointerRelease(pt);
if (retval != 0)
break;
}
if (retval > 0) {
if (outTrack != NULL)
*outTrack = trackNum;
if (outPosition != NULL)
*outPosition = poslast;
if (outEvent != NULL)
*outEvent = ep;
} else if (retval < 0)
retval = 0;
return retval;
}
// Override of the GraphicClientView method. Treats the pencil mode specifically.
- (void)drawSelectRegion
{
int n;
float saveLineWidth;
NSPoint pt1, pt2, dp;
NSRect r;
NSBezierPath *path;
n = [[self dataSource] graphicTool];
if (lineShape == 0 && (n == kGraphicIbeamSelectTool || n == kGraphicRectangleSelectTool)) {
[super drawSelectRegion];
return;
}
// Pencil mode
// Set the line shape (>0): this is the indicator for pencil editing (used in the mouseUp handler)
// lineShape = [[self dataSource] graphicLineShape];
// selectPoints is an instance variable of GraphicClientView
n = [selectPoints count];
if (n == 0)
return;
[[NSColor cyanColor] set];
pt1 = [[selectPoints objectAtIndex: 0] pointValue];
if (n < 2) {
[NSBezierPath fillRect: NSMakeRect(pt1.x - 1, pt1.y - 1, 2, 2)];
return;
}
pt2 = [[selectPoints objectAtIndex: 1] pointValue];
/* Calculate the rect with pt1/pt2 at the corners */
r.origin = pt1;
r.size.width = pt2.x - pt1.x;
r.size.height = pt2.y - pt1.y;
if (r.size.width < 0) {
r.size.width = -r.size.width;
r.origin.x = pt2.x;
}
if (r.size.height < 0) {
r.size.height = -r.size.height;
r.origin.y = pt2.y;
}
saveLineWidth = [NSBezierPath defaultLineWidth];
[NSBezierPath setDefaultLineWidth: 1.0];
[NSBezierPath fillRect: NSMakeRect(pt1.x - 1, pt1.y - 1, 2, 2)];
[NSBezierPath fillRect: NSMakeRect(pt2.x - 1, pt2.y - 1, 2, 2)];
if (lineShape == kGraphicLinearShape) {
[NSBezierPath strokeLineFromPoint: pt1 toPoint: pt2];
} else if (lineShape == kGraphicRandomShape) {
[NSBezierPath strokeRect: NSInsetRect(r, -0.5, -0.5)];
} else {
const float *p;
switch (lineShape) {
case kGraphicParabolaShape:
p = sParabolaPoints;
break;
case kGraphicArcShape:
p = sArcPoints;
break;
case kGraphicSigmoidShape:
p = sSigmoidPoints;
break;
default:
return;
}
dp.x = pt2.x - pt1.x;
dp.y = pt2.y - pt1.y;
path = [NSBezierPath bezierPath];
while (p[2] >= 0.0) {
[path moveToPoint: NSMakePoint(pt1.x + dp.x * p[0], pt1.y + dp.y * p[1])];
[path curveToPoint: NSMakePoint(pt1.x + dp.x * p[6], pt1.y + dp.y * p[7])
controlPoint1: NSMakePoint(pt1.x + dp.x * p[2], pt1.y + dp.y * p[3])
controlPoint2: NSMakePoint(pt1.x + dp.x * p[4], pt1.y + dp.y * p[5])];
p += 6;
}
[path stroke];
/* Eye guide */
if (p[0] < 1.0 || p[1] < 1.0) {
[[[NSColor cyanColor] colorWithAlphaComponent: 0.5] set];
[NSBezierPath strokeLineFromPoint: NSMakePoint(pt1.x + dp.x * p[0] + 0.5, pt1.y + dp.y * p[1] + 0.5) toPoint: NSMakePoint(pt2.x + 0.5, pt2.y + 0.5)];
}
}
[NSBezierPath setDefaultLineWidth: saveLineWidth];
}
// Calculate the value of cubic bezier coordinates from the parameter t
static float
cubicFunc(float t, const float *points)
{
// The control parameters are given as points[0, 2, 4, 6]
float a0, a1, a2, a3;
a0 = -points[0] + 3 * points[2] - 3 * points[4] + points[6];
a1 = 3 * (points[0] - 2 * points[2] + points[4]);
a2 = 3 * (-points[0] + points[2]);
a3 = points[0];
return a3 + t * (a2 + t * (a1 + t * a0));
}
// Calculate the parameter t from the coordinate value
// tt is the hint value for solving the equation.
static float
cubicReverseFunc(float x, const float *points, float tt)
{
double a0, a1, a2, a3, dx, t, dxdt, t0, t1;
int iter;
a0 = -points[0] + 3 * points[2] - 3 * points[4] + points[6];
a1 = 3 * (points[0] - 2 * points[2] + points[4]);
a2 = 3 * (-points[0] + points[2]);
a3 = points[0];
t = tt;
iter = 0;
while (1) {
dx = a3 + t * (a2 + t * (a1 + t * a0)) - x;
if (fabs(dx) < 1e-8)
return t;
dxdt = a2 + t * (2 * a1 + t * 3 * a0);
if (++iter > 10 || fabs(dxdt) < 1e-8 || (t0 = t - dx / dxdt) >= 1.0 || t0 <= 0) {
// Switch to binary search
if (dx < 0) {
t0 = t;
t1 = (a3 > x ? 0 : 1);
} else {
t1 = t;
t0 = (a3 < x ? 0 : 1);
}
while (1) {
t = (t0 + t1) / 2;
dx = a3 + t * (a2 + t * (a1 + t * a0)) - x;
if (dx < -1e-8) {
t0 = t;
} else if (dx > 1e-8) {
t1 = t;
} else return t;
if (fabs(t1 - t0) < 1e-8)
return t;
}
}
if (fabs(t - t0) < 1e-8)
return t;
t = t0;
}
}
// Edit in the pencil mode, i.e. edit the strip chart values according to
// the graphicLineShape and graphicEditingMode.
- (void)doPencilEdit
{
int i, n;
NSPoint pt1, pt2;
MDTickType t1, t2;
MDTickType fromTick, toTick;
MDPointer *mdptr;
float fromValue, toValue;
float pixelsPerTick, height;
const float *p;
int v1, v2;
int editingMode;
BOOL shiftFlag = (([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) != 0);
MyDocument *doc = (MyDocument *)[dataSource document];
// selectPoints is an instance variable of GraphicClientView
n = [selectPoints count];
if (n == 0)
return;
pt1 = [[selectPoints objectAtIndex: 0] pointValue];
if (n < 2)
pt2 = pt1;
else pt2 = [[selectPoints objectAtIndex: 1] pointValue];
pixelsPerTick = [dataSource pixelsPerTick];
height = [self bounds].size.height;
t1 = floor(pt1.x / pixelsPerTick + 0.5);
t2 = floor(pt2.x / pixelsPerTick + 0.5);
v1 = floor(pt1.y * (maxValue - minValue) / height + 0.5);
v2 = floor(pt2.y * (maxValue - minValue) / height + 0.5);
if (t1 < t2) {
fromTick = t1;
toTick = t2;
fromValue = v1;
toValue = v2;
} else {
fromTick = t2;
toTick = t1;
fromValue = v2;
toValue = v1;
}
if (t1 == t2 || lineShape == kGraphicRandomShape) {
// Let fromValue <= toValue
if (fromValue > toValue) {
float vw = fromValue;
fromValue = toValue;
toValue = vw;
}
}
switch (lineShape) {
case kGraphicParabolaShape:
p = sParabolaPoints;
break;
case kGraphicArcShape:
p = sArcPoints;
break;
case kGraphicSigmoidShape:
p = sSigmoidPoints;
break;
default:
p = NULL;
break;
}
editingMode = [[self dataSource] graphicEditingMode];
if (editingMode == kGraphicSetMode && eventKind != kMDEventNote && eventKind != kMDEventInternalNoteOff && !shiftFlag) {
// Generate a series of events
MDTrackObject *trackObj;
MDEvent event;
trackObj = [[[MDTrackObject allocWithZone: [self zone]] init] autorelease];
mdptr = MDPointerNew([trackObj track]);
MDEventInit(&event);
MDSetKind(&event, eventKind);
MDSetCode(&event, eventCode);
if (t1 == t2 || v1 == v2 || lineShape == kGraphicLinearShape || lineShape == kGraphicRandomShape) {
MDTickType tick;
tick = fromTick;
while (1) {
float v;
MDTickType tick2;
if (t1 == t2)
v = 1.0;
else if (lineShape == kGraphicRandomShape)
v = (random() % 0x10000000) / (float)0x10000000;
else
v = (double)(tick - fromTick) / (toTick - fromTick);
v = v * (toValue - fromValue) + fromValue;
// Generate an event
MDSetTick(&event, tick);
if (eventKind == kMDEventTempo)
MDSetTempo(&event, floor(v));
else
MDSetData1(&event, (int)floor(v));
MDPointerInsertAnEvent(mdptr, &event);
// NSLog(@"tick=%ld value=%d", tick, (int)floor(v));
if (tick >= toTick)
break;
tick2 = MDCalibratorTimeToTick(calib, MDCalibratorTickToTime(calib, tick) + sTimeResolution);
if (tick2 < tick + sTickResolution)
tick2 = tick + sTickResolution;
if (lineShape == kGraphicLinearShape) {
MDTickType tick3;
if (fabs(toValue - fromValue) < 1e-6)
tick3 = toTick;
else
tick3 = tick + floor(fabs(sValueResolution / (toValue - fromValue) * (toTick - fromTick)));
if (tick3 > tick2)
tick2 = tick3;
}
if (tick2 > toTick)
tick = toTick;
else tick = tick2;
}
} else if (p != NULL) {
float x, y, v, t;
int n;
n = 0;
while (p[2] >= 0.0) {
// Initial point
t = 0.0;
x = p[0];
y = p[1];
while (1) {
float ta, tb, tc;
float xa, xb, yc;
MDTimeType tm;
MDTickType tk;
if (n == 0 || t > 0.0) {
// Generate an event
MDSetTick(&event, (MDTickType)(x * (t2 - t1) + t1));
v = y * (v2 - v1) + v1;
if (eventKind == kMDEventTempo)
MDSetTempo(&event, floor(v));
else
MDSetData1(&event, (int)floor(v));
MDPointerInsertAnEvent(mdptr, &event);
// NSLog(@"t=%f tick=%ld value=%d", t, (MDTickType)(x * (t2 - t1) + t1), (int)floor(v));
}
if (t >= 1.0)
break;
xa = x + fabs((double)sTickResolution / (t2 - t1));
if (xa < p[6]) {
ta = cubicReverseFunc(xa, p, t);
} else ta = 1.0;
tm = MDCalibratorTickToTime(calib, (MDTickType)(x * (t2 - t1) + t1));
if (t2 > t1)
tm += sTimeResolution;
else
tm -= sTimeResolution;
tk = MDCalibratorTimeToTick(calib, tm);
xb = ((double)(tk - t1)) / (t2 - t1);
// xb = (double)(MDCalibratorTimeToTick(calib,
// MDCalibratorTickToTime(calib, (MDTickType)(x * (t2 - t1) + t1))
// + sTimeResolution) - t1) / (t2 - t1);
if (xb <= x) { // This can happen due to the round-off of tk
tb = t;
} else if (xb < p[6]) {
tb = cubicReverseFunc(xb, p, t);
} else tb = 1.0;
if (fabs(v2 - v1) < 1.0 || p[1] == p[7]) {
tc = 1.0;
} else {
yc = (double)sValueResolution / (v2 - v1);
if (p[1] < p[7]) {
yc = y + fabs(yc);
if (yc < p[7])
tc = cubicReverseFunc(yc, p + 1, t);
else tc = 1.0;
} else {
yc = y - fabs(yc);
if (yc > p[7])
tc = cubicReverseFunc(yc, p + 1, t);
else tc = 1.0;
}
}
t = (ta < tb ? tb : ta);
t = (t < tc ? tc : t);
x = cubicFunc(t, p);
y = cubicFunc(t, p + 1);
}
p += 6;
n++;
}
}
MDPointerRelease(mdptr);
// MDTrackCheck([trackObj track]);
if (MDTrackGetNumberOfEvents([trackObj track]) == 0)
return;
for (i = 0; (n = [dataSource sortedTrackNumberAtIndex: i]) >= 0; i++) {
MDPointSet *insertedPositionSet;
if (eventKind == kMDEventTempo && n != 0)
continue;
if (eventKind != kMDEventTempo && n == 0)
continue;
if ([dataSource isFocusTrack: n]) {
MDPointSetObject *psetObj = [doc eventSetInTrack: n eventKind: eventKind eventCode: eventCode fromTick: fromTick toTick: toTick fromData: kMDMinData toData: kMDMaxData inPointSet: nil];
[doc deleteMultipleEventsAt: psetObj fromTrack: n deletedEvents: NULL];
if ([doc insertMultipleEvents: trackObj at: nil toTrack: n selectInsertedEvents: YES insertedPositions: &insertedPositionSet] && insertedPositionSet != NULL) {
if (!shiftFlag) {
MDSelectionObject *selObj = [[MDSelectionObject alloc] initWithMDPointSet:insertedPositionSet];
[doc setSelection:selObj inTrack:n sender:self];
[selObj release];
}
}
}
}
} else {
// Modify the data of the existing events
for (i = 0; (n = [dataSource sortedTrackNumberAtIndex: i]) >= 0; i++) {
MDEvent *ep;
MDTrack *track;
MDSelectionObject *psetObj;
MDPointSet *pset;
long idx, count, j;
NSMutableData *theData;
float *fp;
float x, y, t, v, v0;
if (![dataSource isFocusTrack: n])
continue;
if (eventKind == kMDEventTempo && n != 0)
continue;
if (eventKind != kMDEventTempo && n == 0)
continue;
track = [[[dataSource document] myMIDISequence] getTrackAtIndex: n];
if (track == NULL)
continue;
psetObj = [doc eventSetInTrack: n eventKind: (eventKind == kMDEventInternalNoteOff ? kMDEventNote : eventKind) eventCode: eventCode fromTick: fromTick toTick: toTick fromData: kMDMinData toData: kMDMaxData inPointSet: (shiftFlag ? [doc selectionOfTrack: n] : nil)];
if (psetObj == nil)
continue;
pset = [psetObj pointSet];
count = MDPointSetGetCount(pset);
if (count == 0)
continue;
theData = [NSMutableData dataWithLength: count * sizeof(float)];
fp = (float *)[theData mutableBytes];
mdptr = MDPointerNew(track);
idx = -1;
if (t1 < t2)
t = 0;
else t = 1;
for (j = 0; j < count; j++) {
ep = MDPointerForwardWithPointSet(mdptr, [psetObj pointSet], &idx);
if (ep == NULL)
break;
if (t1 == t2) {
v = toValue;
} else {
x = ((double)MDGetTick(ep) - t1) / (t2 - t1);
if (lineShape == kGraphicLinearShape)
y = x;
else if (lineShape == kGraphicRandomShape)
y = (random() % 0x10000000) / (float)0x10000000;
else if (p != NULL) {
const float *pp;
for (pp = p; pp[2] >= 0; pp += 6) {
if (pp[0] <= x && x <= pp[6])
break;
}
if (pp[2] >= 0) {
t = cubicReverseFunc(x, pp, t);
y = cubicFunc(t, pp + 1);
} else break;
} else break;
v = floor(y * (v2 - v1) + v1 + 0.5);
}
switch (eventKind) {
case kMDEventNote:
v0 = MDGetNoteOnVelocity(ep);
break;
case kMDEventInternalNoteOff:
v0 = MDGetNoteOffVelocity(ep);
break;
case kMDEventTempo:
v0 = MDGetTempo(ep);
break;
default:
v0 = MDGetData1(ep);
break;
}
if (editingMode == kGraphicAddMode) {
/* The vertical center will be zero */
v = v0 + v - (maxValue - minValue + 1) * 0.5;
} else if (editingMode == kGraphicScaleMode) {
/* The full scale is 0..200% */
v = v0 * (v - minValue) / (maxValue - minValue + 1) * 2.0;
} else if (editingMode == kGraphicLimitMaxMode) {
if (v0 < v)
v = v0;
} else if (editingMode == kGraphicLimitMinMode) {
if (v0 > v)
v = v0;
}
if (v < minValue)
v = minValue;
else if (v > maxValue)
v = maxValue;
fp[j] = v;
}
MDPointerRelease(mdptr);
if ([doc modifyData: theData forEventKind: eventKind ofMultipleEventsAt: psetObj inTrack: n mode: MyDocumentModifySet]) {
if (!shiftFlag) {
[doc setSelection:psetObj inTrack:n sender:self];
}
}
}
}
}
- (void)doMouseMoved: (NSEvent *)theEvent
{
int track;
long pos;
MDEvent *ep;
int n, tool;
NSPoint pt = [NSEvent mouseLocation]; /* Use mouseLocation in case this is called from flagsChanged: handler (not implemented yet) */
pt = [self convertPoint: [[self window] convertScreenToBase:pt] fromView: nil];
tool = [[self dataSource] graphicTool];
if (tool == kGraphicPencilTool || (tool == kGraphicRectangleSelectTool && ([theEvent modifierFlags] & NSCommandKeyMask) != 0)) {
[[NSCursor pencilCursor] set];
return;
}
n = [self findStripUnderPoint: pt track: &track position: &pos mdEvent: &ep];
switch (n) {
case 1:
[[NSCursor moveAroundCursor] set];
return;
case 2:
[[NSCursor horizontalMoveCursor] set];
return;
case 3:
[[NSCursor verticalMoveCursor] set];
return;
}
[super doMouseMoved: theEvent];
}
- (void)doMouseDown: (NSEvent *)theEvent
{
long pos;
MDEvent *ep;
int track;
NSRect bounds;
NSPoint pt;
if (localGraphicTool == kGraphicRectangleSelectTool && ([theEvent modifierFlags] & NSCommandKeyMask) != 0)
localGraphicTool = kGraphicPencilTool;
if (localGraphicTool == kGraphicPencilTool) {
// Invoke the common dragging procedure without checking mouse hitting on the existing chart
// The overridden method drawSelectRegion: implements the specific treatment
// for this class.
[super doMouseDown: theEvent];
lineShape = [[self dataSource] graphicLineShape];
return;
}
lineShape = 0; /* Reset the line shape */
pt = [self convertPoint: [theEvent locationInWindow] fromView: nil];
stripDraggingMode = [self findStripUnderPoint: pt track: &track position: &pos mdEvent: &ep];
pt.x = [dataSource quantizedPixelFromPixel: pt.x];
if (stripDraggingMode > 0) {
float pixelQuantum;
MyDocument *document = [dataSource document];
if (![document isSelectedAtPosition: pos inTrack: track]) {
// int i, n;
// for (i = 0; (n = [dataSource sortedTrackNumberAtIndex: i]) >= 0; i++)
// [document unselectAllEventsInTrack: n sender: self];
[document unselectAllEventsInAllTracks: self];
[document selectEventAtPosition: pos inTrack: track sender: self];
}
draggingStartPoint = draggingPoint = pt;
horizontal = (stripDraggingMode == 2);
[self displayIfNeeded];
// Calculate limit rectangle for the dragging point
selectionRect = [self boundRectForSelection];
bounds = [self bounds];
limitRect = NSMakeRect(
pt.x - (selectionRect.origin.x - bounds.origin.x),
0,
bounds.size.width - selectionRect.size.width,
bounds.size.height);
pixelQuantum = [dataSource pixelQuantum];
limitRect.origin.x = [dataSource quantizedPixelFromPixel: limitRect.origin.x];
if (limitRect.origin.x < 0.0)
limitRect.origin.x += pixelQuantum;
limitRect.size.width = floor(limitRect.size.width / pixelQuantum) * pixelQuantum;
if (limitRect.size.width + limitRect.origin.x > bounds.origin.x + bounds.size.width)
limitRect.size.width -= pixelQuantum;
// [super doMouseDown: theEvent];
return;
}
[super doMouseDown: theEvent];
}
- (void)doMouseDragged: (NSEvent *)theEvent
{
// Pencil mode: invoke the common dragging procedure
// The overridden method drawSelectRegion: implements the specific treatment
// for this class.
if (lineShape > 0) {
[super doMouseDragged: theEvent];
return;
}
if (stripDraggingMode > 0) {
NSPoint pt = [self convertPoint: [theEvent locationInWindow] fromView: nil];
BOOL optionDown = (([theEvent modifierFlags] & NSAlternateKeyMask) != 0);
pt.x = [dataSource quantizedPixelFromPixel: pt.x];
// Support autoscroll (cf. GraphicClientView.mouseDragged)
if (autoscrollTimer != nil) {
[autoscrollTimer invalidate];
[autoscrollTimer release];
autoscrollTimer = nil;
}
if (stripDraggingMode == 1) {
/* horizontal or vertical, depending on the mouse position */
NSSize delta;
delta.width = fabs(pt.x - draggingStartPoint.x);
delta.height = fabs(pt.y - draggingStartPoint.y);
if (delta.width > 3 || delta.height > 3)
horizontal = (delta.width > delta.height);
if (horizontal) {
if (optionDown)
[[NSCursor horizontalMovePlusCursor] set];
else
[[NSCursor horizontalMoveCursor] set];
} else {
/* Note: no duplicate on vertical move with option key */
[[NSCursor verticalMoveCursor] set];
}
}
if (horizontal)
pt.y = draggingStartPoint.y;
else
pt.x = draggingStartPoint.x;
pt = [self convertPoint: pt toView: nil];
// [self invalidateDraggingRegion];
if ([self autoscroll: [theEvent mouseEventWithLocation: pt]])
autoscrollTimer = [[NSTimer scheduledTimerWithTimeInterval: 0.2 target: self selector:@selector(autoscrollTimerCallback:) userInfo: theEvent repeats: NO] retain];
pt = [self convertPoint: pt fromView: nil];
if (pt.x < limitRect.origin.x)
pt.x = limitRect.origin.x;
else if (pt.x > limitRect.origin.x + limitRect.size.width)
pt.x = limitRect.origin.x + limitRect.size.width;
if (pt.y < limitRect.origin.y)
pt.y = limitRect.origin.y;
else if (pt.y > limitRect.origin.y + limitRect.size.height)
pt.y = limitRect.origin.y + limitRect.size.height;
[self invalidateDraggingRegion];
draggingPoint = pt;
[self invalidateDraggingRegion];
[self displayIfNeeded];
} else [super doMouseDragged: theEvent];
}
- (void)doMouseUp: (NSEvent *)theEvent
{
int i, trackNo;
float ppt;
float height;
MyDocument *document;
MDTickType minTick, maxTick;
NSRect bounds;
// Pencil mode: edit the strip chart values according to the line shape and
// editing mode
if (lineShape > 0) {
[self doPencilEdit];
lineShape = 0;
return;
}
if (stripDraggingMode > 0) {
NSPoint pt;
MDTickType deltaTick;
float deltaValue;
BOOL optionDown = (([theEvent modifierFlags] & NSAlternateKeyMask) != 0);
[self invalidateDraggingRegion];
pt.x = draggingPoint.x - draggingStartPoint.x;
pt.y = draggingPoint.y - draggingStartPoint.y;
deltaTick = floor(pt.x / [dataSource pixelsPerTick] + 0.5);
deltaValue = pt.y * (maxValue - minValue) / [self bounds].size.height;
[dataSource dragEventsOfKind: eventKind andCode: eventCode byTick: deltaTick andValue: deltaValue sender: self optionFlag: optionDown];
stripDraggingMode = 0;
[self displayIfNeeded];
return;
} else if (!isDragging || isLoupeDragging) {
[super doMouseUp: theEvent];
return;
}
/* Change selection */
bounds = [[self selectionPath] bounds];
height = [self bounds].size.height;
ppt = [dataSource pixelsPerTick];
minTick = (MDTickType)(bounds.origin.x / ppt);
maxTick = (MDTickType)((bounds.origin.x + bounds.size.width) / ppt);
document = (MyDocument *)[dataSource document];
for (i = 0; (trackNo = [dataSource sortedTrackNumberAtIndex: i]) >= 0; i++) {
MDTrack *track;
MDPointer *pt;
MDEvent *ep;
MDPointSet *pset;
MDSelectionObject *obj;
if (![dataSource isFocusTrack: trackNo])
continue;
track = [[document myMIDISequence] getTrackAtIndex: trackNo];
if (track == NULL)
continue;
pt = MDPointerNew(track);
if (pt == NULL)
break;
pset = MDPointSetNew();
if (pset == NULL)
break;
MDPointerJumpToTick(pt, minTick);
MDPointerBackward(pt);
while ((ep = MDPointerForward(pt)) != NULL && MDGetTick(ep) < maxTick) {
NSPoint point;
if (MDGetKind(ep) != eventKind)
continue;
if (eventCode != -1 && MDGetCode(ep) != eventCode)
continue;
point.y = ceil((getYValue(ep, eventKind) - minValue) / (maxValue - minValue) * height);
point.x = floor(MDGetTick(ep) * ppt);
// point.x = floor(MDGetTick(ep) * ppt);
// point.y = floor(MDGetCode(ep) * ys + 0.5) + 0.5 * ys;
if ([self isPointInSelectRegion: point]) {
if (MDPointSetAdd(pset, MDPointerGetPosition(pt), 1) != kMDNoError)
break;
}
}
obj = [[MDSelectionObject allocWithZone: [self zone]] initWithMDPointSet: pset];
if (currentModifierFlags & NSShiftKeyMask) {
[document toggleSelection: obj inTrack: trackNo sender: self];
} else {
[document setSelection: obj inTrack: trackNo sender: self];
}
[obj release];
MDPointSetRelease(pset);
MDPointerRelease(pt);
}
}
@end