This is EnvelopeView.m in view mode; [Download] [Up]
/**************************************************
* SynthBuilder
* Copyright 1993 Nick Porcaro All Rights Reserved
**************************************************/
/* EnvelopeView.m -- Implementation of EnvelopeView class */
// Revision History:
// 8/25/92 Add parsing of exponential format numbers
// 8/21/92 Memory allocation bug on allocateTemp and allocateDraw
// 8/20/92 Added drawSegments option.
// 8/20/93 Took out NXDefaults stuff and adapted (Nick Porcaro)
#import "EnvelopeView.h"
#import "EnvController.h"
#import <stdlib.h>
#import <ctype.h>
#import <objc/zone.h>
#import <defaults/defaults.h>
#import <appkit/Application.h>
#import <musickit/musickit.h> // for envelope class stuff
#import <dpsclient/wraps.h> // for PSxxx stuff
#import <appkit/Form.h>
#import <appkit/Button.h>
#import <appkit/Panel.h>
#import <appkit/NXCursor.h>
#import <appkit/NXImage.h>
#import <appkit/Pasteboard.h>
#define STYLE_MK '0'
#define STYLE_pX_Yp '1'
#define STYLE_pXcYp '2'
#define STYLE_X_Y '3'
#define STYLE_XcY '4'
#define STYLE_X_Y_Zc '5'
#define STYLE_XcYcZ_ '6'
#define STYLE_ppX_Yp '7'
#define TEST 1
#define KNOBSIZE 8
#define MAXNUMSEGS 8
#define WIDTH bounds.size.width
#define HEIGHT bounds.size.height
#define WHITE 1.0
#define BLACK 0.0
#define DKGRAY (1.0/3.0)
#define LTGRAY (2.0/3.0)
#define TRANSP 2.0
#define DRAG_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK)
//===================================================================
// Envelope auxiliar routines and macros
//===================================================================
#ifdef USE_DEFAULTS
NXDefaultsVector envelopeDefaults={
{"defaultSmoothing", "1.0" },
{"showSmoothing", "YES" },
{"drawSegments", "YES" },
};
#endif
//-------------------------------------------------------------------
// Draw a knob in the display
#define DRAWKNOB(x,y) PScompositerect(x-KNOBSIZE/2,y-KNOBSIZE/2,KNOBSIZE, KNOBSIZE,NX_SOVER)
//-------------------------------------------------------------------
// Translate from x/y envelope values to display pixel coordinates
#define xToPix(x) (((x-xMin)/(xMax-xMin))*WIDTH)
#define yToPix(y) (((y-yMin)/(yMax-yMin))*HEIGHT)
//-------------------------------------------------------------------
// Translate from display pixel coordinates to x/y envelope values
#define pixToX(xPix) (xPix/WIDTH*(xMax-xMin)+xMin)
#define pixToY(yPix) (yPix/HEIGHT*(yMax-yMin)+yMin)
//-------------------------------------------------------------------
// Shorthand to access envelope object instance variables
#define pointCount [theEnvelope pointCount]
#define stickyPoint [theEnvelope stickPoint]
#define xValues [theEnvelope xArray]
#define yValues [theEnvelope yArray]
#define sValues [theEnvelope smoothingArray]
//-------------------------------------------------------------------
// Temporary copy arrays for envelope copy operations
typedef struct _env {
double *x; // x array for adding or removing points
double *y; // y array
double *s; // smoothing array
int max; // currently allocated length of arrays
} Env;
static Env * temp=NULL; // only one copy for each envelope object
//===================================================================
@implementation EnvelopeView
//===================================================================
// Allocate or reallocate arrays to "size" elements
- allocateTemp:(int) size
{
int newsize=size;
if (newsize<64) newsize=64;
if (temp==NULL) { // initial creation of arrays
temp=NXZoneMalloc([self zone], sizeof(Env));
temp->x=NXZoneMalloc([self zone], sizeof(double)*newsize);
temp->y=NXZoneMalloc([self zone], sizeof(double)*newsize);
temp->s=NXZoneMalloc([self zone], sizeof(double)*newsize);
temp->max=newsize;
}
else if (size>temp->max) { // grow to double size each time
if (size>temp->max*2)
temp->max=size;
else
temp->max*=2;
temp->x=NXZoneRealloc([self zone], temp->x,sizeof(double)*temp->max);
temp->y=NXZoneRealloc([self zone], temp->y,sizeof(double)*temp->max);
temp->s=NXZoneRealloc([self zone], temp->s,sizeof(double)*temp->max);
}
return self;
}
// Free allocated memory
- freeTemp
{
if (temp!=NULL) {
NXZoneFree([self zone], temp->x);
NXZoneFree([self zone], temp->y);
NXZoneFree([self zone], temp->s);
NXZoneFree([self zone], temp);
temp=NULL;
}
return self;
}
//-------------------------------------------------------------------
// Temporary arrays for drawing operations
typedef struct _draw {
int num; // number of points in draw arrays (x,y,p)
int *p; // point number array
double *x; // x array of drawing coordinates
double *y; // y array
double *yr; // real y coordinate of each point
int max; // currently allocated length
} Draw;
// Each time the envelope is drawn in the screen the calculated positions
// of all intermediate points are stored in the x and y arrays. The point
// number is also stored in the p array. This arrays are then used to erase
// the lines by drawing them in white.
static Draw * draw=NULL;
// Allocate or reallocate arrays to "size" elements
- allocateDraw:(int) size
{
int newsize=size;
if (newsize<64) newsize=64;
if (draw==NULL) {
draw=NXZoneMalloc([self zone], sizeof(Draw));
draw->x=NXZoneMalloc([self zone], sizeof(double)*newsize*MAXNUMSEGS);
draw->y=NXZoneMalloc([self zone], sizeof(double)*newsize*MAXNUMSEGS);
draw->p=NXZoneMalloc([self zone], sizeof(int)*newsize*MAXNUMSEGS);
draw->yr=NXZoneMalloc([self zone], sizeof(double)*newsize);
draw->max=newsize;
draw->num=0;
}
else if (size>draw->max) {
if (size>draw->max*2)
draw->max=size;
else
draw->max*=2;
draw->x=NXZoneRealloc([self zone], draw->x,sizeof(double)*draw->max*MAXNUMSEGS);
draw->y=NXZoneRealloc([self zone], draw->y,sizeof(double)*draw->max*MAXNUMSEGS);
draw->p=NXZoneRealloc([self zone], draw->p,sizeof(int)*draw->max*MAXNUMSEGS);
draw->yr=NXZoneRealloc([self zone], draw->yr,sizeof(double)*draw->max);
}
return self;
}
// Free allocated memory
- freeDraw
{
if (draw!=NULL) {
NXZoneFree([self zone], draw->x);
NXZoneFree([self zone], draw->y);
NXZoneFree([self zone], draw->p);
NXZoneFree([self zone], draw->yr);
NXZoneFree([self zone], draw);
draw=NULL;
}
return self;
}
- envelope
{
return theEnvelope;
}
- setNewEnvelope:anEnv
{
if ( theEnvelope && (theEnvelope != anEnv))
{
[theEnvelope free];
}
theEnvelope = anEnv;
[theController update: self];
return self;
}
//-------------------------------------------------------------------
// - resetCursorRects sets a new cursor of the view. The message first
// initializes the two cursor shapes if needed and the proceeds to
// set the default cursor for the view to a cross.
- resetCursorRects
{
NXRect visible;
NXPoint spot;
if (!theCross) {
theCross=[NXCursor newFromImage:[NXImage newFromSection:"cross.tiff"]];
spot.x = 7.0;
spot.y = 7.0;
[theCross setHotSpot:&spot];
}
if (!theFilledCross) {
theFilledCross=[NXCursor newFromImage:[NXImage newFromSection:"crossfill.tiff"]];
spot.x = 7.0;
spot.y = 7.0;
[theFilledCross setHotSpot:&spot];
}
if ([self getVisibleRect:&visible]) {
[self addCursorRect:&visible cursor: theCross];
}
return self;
}
//-------------------------------------------------------------------
// - selectPoint:n
// Selects point number n in the envelope and broadcasts the change
// to the controller object.
- selectPoint:(int)n
{
selected=n;
[self display];
[theController update: self];
return self;
}
//-------------------------------------------------------------------
// - initFrame: -- initialize a new Envelope View object
- initFrame:(const NXRect *)frameRect
{
static double xs[]={0.0,1.0};
static double ys[]={0.0,0.0};
static double ss[]={1.0,1.0};
/* malloc_debug(0xFFFF); */
#ifdef USE_DEFAULTS
defaultSmooth=atof(NXGetDefaultValue("EnvelopeEd","defaultSmoothing"));
#endif
defaultSmooth = 1.0;
ss[0]=ss[1]=defaultSmooth;
self = [super initFrame: frameRect];
theEnvelope=[[[Envelope alloc] init]
setPointCount: 2
xArray: xs
orSamplingPeriod: 1.0
yArray: ys
smoothingArray: ss
orDefaultSmoothing: defaultSmooth];
[theEnvelope setStickPoint: MAXINT]; // init new envelope to default values
[self allocateDraw:64]; // arrays for drawing and erasing
[self allocateTemp:64]; // temporary arrays for move operations
userPath = newUserPath(); // allocate a user path for drawing
xMin=yMin=0.0; // max and min limits of display
xMax=yMax=1.0;
xSnap=ySnap=0.001; // initial Snap off in both directions
envColour=BLACK;
#ifdef USE_DEFAULTS
if (strcasecmp(NXGetDefaultValue("EnvelopeEd","showSmoothing"),"YES")==0)
showSmooth=-1;
if (strcasecmp(NXGetDefaultValue("EnvelopeEd","drawSegments"),"YES")==0)
drawSegments=-1;
else showSmooth=0;
#endif
showSmooth = 1;
drawSegments = 1;
[self selectPoint: 0]; // select first point and display
return self;
}
//-------------------------------------------------------------------
// - free -- Release the allocated memory when object is freed
-free
{
[theEnvelope free];
theEnvelope = nil;
freeUserPath(userPath);
[self freeTemp];
[self freeDraw];
return [super free];
}
//-------------------------------------------------------------------
// - controllerIs: Receive the object id of the controller of the view
- controllerIs:sender
{
theController=sender;
return self;
}
//-------------------------------------------------------------------
// - insertPointAt:p
// Finds the place in the envelope where the point p should be inserted.
// Returns the point number at the insertion or -1 if an error is
// detected.
- (int) insertPointAt:(NXPoint)p
{
int point;
point=0;
while (point<=pointCount-1) {
if (xValues[point]>p.x)
return point;
point++;
}
return pointCount;
}
//-------------------------------------------------------------------
// - (int) hitKnobAt: p border: delta
// Test if the user hits a knob when clicking the mouse at point p.
// The boundaries of the zone are delta pixels wide. Returns the
// number of the hit point or -1 if no hit.
- (int) hitKnobAt:(NXPoint)p border:(float)delta;
{
int point;
float kx, ky, dx, dy;
dx=delta/WIDTH*(xMax-xMin);
dy=delta/HEIGHT*(yMax-yMin);
for (point=0; point<pointCount; point++) {
kx=xValues[point];
ky=yValues[point];
if (p.x<kx-dx)
break;
if (p.x<=kx+dx && p.x>=kx-dx &&
p.y<=ky+dy && p.y>=ky-dy)
return point;
}
return -1;
}
//-------------------------------------------------------------------
// drawKnobs(from,to,lcolour,hcolour) draws a set of knobs starting at
// point from and ending at point to. Also highlights the selected
// knob if necessary. The second colour argument specifies the colour
// of the highlighted knob.
- drawKnobsFrom:(int)from to:(int)to in:(float)lcolour :(float)hcolour
{
int point;
PSsetgray(lcolour);
for (point=from; point<=to; point++) {
if (point!=selected)
DRAWKNOB(xToPix(xValues[point]),yToPix(yValues[point]));
}
if (selected>=from && selected<=to && hcolour<=WHITE) {
PSsetgray(hcolour);
DRAWKNOB(xToPix(xValues[selected]),yToPix(yValues[selected]));
}
return self;
}
//-------------------------------------------------------------------
// drawSegments(from,to,colour) draws line segments starting at point
// from and ending at point to. It also takes care of the sticky point
// marker. Due to problems with rounding each time a path is drawn the
// moves are recorded in a set of arrays. This path is used to "erase"
// the segment when a point is moved.
- recordMovex:(double)x y:(double)y p:(int)n draw:(int)state
{
double xp, yp;
draw->p[draw->num]=n;
draw->x[draw->num]=xp=xToPix(x);
draw->y[draw->num++]=yp=yToPix(y);
if (state==0)
UPmoveto(userPath,xp,yp);
else
UPlineto(userPath,xp,yp);
return self;
}
- drawSegmentsFrom:(int)from to:(int)to in:(float)colour
{
int point, seg;
double
incx, incy,
deltax, deltay,
xi, xf, x,
yi, yf, y,
smooth, numsegs;
// If drawing colour is white then use erase arrays to remove
// the previously drawn segments
if (colour==WHITE) {
PSsetgray(colour);
beginUserPath(userPath, NO);
for (point=0;
draw->p[point]<=from && point<draw->num;
point++);
if (point-1>=0) point--;
UPmoveto(userPath,draw->x[point],draw->y[point]);
for (;
draw->p[point]<=to && point<draw->num;
point++)
UPlineto(userPath,draw->x[point],draw->y[point]);
endUserPath(userPath, dps_ustroke);
sendUserPath(userPath);
return self;
}
// Else construct path for next erase and draw segments
// first draw x and y axis if necessary
draw->num=0;
if (xMin!=0 || xMax!=0) {
PSsetgray(LTGRAY);
beginUserPath(userPath, NO);
if (xMin!=0) {
[self recordMovex: 0 y: yMin p: from draw: 0];
[self recordMovex: 0 y: yMax p: from draw: 1];
}
if (yMin!=0) {
[self recordMovex: xMin y: 0 p: from draw: 0];
[self recordMovex: xMax y: 0 p: from draw: 1];
}
endUserPath(userPath, dps_ustroke);
sendUserPath(userPath);
}
// and then draw the segments that interconnect the breakpoints.
if (drawSegments==0) return self;
draw->num=0;
PSsetgray(colour);
beginUserPath(userPath, NO);
[self recordMovex: xValues[from] y: yValues[from] p: from draw: 0];
yi=draw->yr[from]=yValues[from];
for (point=from+1; point<=to; point++) {
xi=xValues[point-1];
xf=x=xValues[point];
yf=y=yValues[point];
deltax=xf-xi;
deltay=yf-yi;
numsegs=rint((deltax*WIDTH/(xMax-xMin))/3); // at least 3 pixels per segment
if (numsegs>MAXNUMSEGS) numsegs=MAXNUMSEGS; // but no more than MAXNUMSEGS...
if (numsegs<2) numsegs=2; // or less than 2
if (showSmooth!=0 && deltax!=0 && deltay!=0) {
smooth=sValues[point];
incx=deltax/numsegs; // effective deltax is always
if (smooth<0.01) incx*=0.01; // determined by the time
else if (smooth<1.0) incx*=smooth; // constant of the segment
incy=deltay/numsegs;
for (seg=0, x=xi+incx; seg<numsegs-1; seg++, x+=incx) {
y=yi+(deltay*(1-exp(-5.5262*(x-xi)/(deltax*smooth))));
[self recordMovex: x y: y p: point draw: 1];
}
y=yi+(deltay*(1-exp(-5.5262/smooth))); // last segment to xf directly
[self recordMovex: xf y: y p: point draw: 1];
}
else [self recordMovex: x y: y p: point draw: 1];
if (point==stickyPoint) {
[self recordMovex: xValues[point] y: 0 p: point draw: 1];
[self recordMovex: xValues[point] y: y p: point draw: 0];
}
yi=draw->yr[point]=y;
}
endUserPath(userPath, dps_ustroke);
sendUserPath(userPath);
return self;
}
//-------------------------------------------------------------------
// - drawSelf:: - Draw the envelope object in the view
- drawSelf:(NXRect *)rects :(int)rectCount
{
NXEraseRect(&bounds); // clear the view
PSsetlinewidth(0.0);
if (theEnvelope!=NULL) {
[self drawKnobsFrom:0 to:pointCount-1 in:LTGRAY:envColour];
[self drawSegmentsFrom: 0 to:pointCount-1 in: envColour];
}
/* Nick added the following line
* so a fakeUG argument would be updated
* whenever the envelope is redrawn
*/
[theController envelopeDrawn];
#ifdef DEBUG_PARAM
printf("EnvelopeView:drawSelf:calling envelopeDrawn\n");
#endif DEBUG_PARAM
return self;
}
//-------------------------------------------------------------------
// - eraseSelectedKnob Draw the envelope again without drawing the
// selected knob. This is used just before the modal loop for draging
// a point is entered. In the loop the cursor itself acts as the knob.
- eraseSelectedKnob
{
NXEraseRect(&bounds); // clear the view
PSsetlinewidth(0.0);
if (theEnvelope!=NULL) {
[self drawKnobsFrom:0 to:pointCount-1 in:LTGRAY:TRANSP];
[self drawSegmentsFrom: 0 to:pointCount-1 in: envColour];
}
return self;
}
//-------------------------------------------------------------------
// movePoint:to: Move point n to a new location.
// This is called from within the drag modal loop so it tries to be
// quick. Each time it erases the old knobs and segments (up to two)
// and then draws the new knobs and segments. This last step can draw
// any number of segments depending on how close the other points are
// to the selected point (the problem is that the knob can erase
// small portions of the segments that are lying outside of the area
// bounded by the selected point and its two enclosing points).
- (int) movePoint:(int)n to: (NXPoint)p;
{
int left,right, // limits for x movement of point
drawFrom,drawTo; // start and end points for drawing lines
if (n==0) { // determine limits for erase and draw
drawFrom=0;
drawTo=1;
}
else
if (n==pointCount-1) {
drawFrom=n-1;
drawTo=n;
}
else {
drawFrom=n-1;
drawTo=n+1;
}
left=drawFrom;
right=drawTo;
if (showSmooth!=0) { // if showing smoothing draw points...
while(drawFrom-1>=0 && // ...that have smoothing greater that 1
sValues[drawFrom]>1.0)
drawFrom--;
while(drawTo+1<pointCount &&
sValues[drawTo]>1.0)
drawTo++;
}
if (n!=0 && p.x<xValues[left]) // force selected point to be within
p.x=xValues[left]; // neighbouring points
if (n!=pointCount-1 && p.x>xValues[right])
p.x=xValues[right];
if (xSnap!=0 &&
xValues[right]-xValues[left]>2*xSnap)
p.x=rint(p.x/xSnap)*xSnap; // snap into x grid
if (p.y>yMax) p.y=yMax; // clip y values to max and min
if (p.y<yMin) p.y=yMin;
if (ySnap!=0)
p.y=rint(p.y/ySnap)*ySnap; // snap into y grid
if (n>1) // avoid 3 points with the same x value
if (p.x==xValues[n-1] &&
p.x==xValues[n-2])
p.x=xValues[n-1]+(1/WIDTH*(xMax-xMin));
if (n<(pointCount-2))
if (p.x==xValues[n+1] &&
p.x==xValues[n+2])
p.x=xValues[n+1]-(1/WIDTH*(xMax-xMin));
[self drawKnobsFrom:drawFrom // erase old knobs and segments
to:drawTo in:WHITE:TRANSP];
[self drawSegmentsFrom:drawFrom
to:drawTo in:WHITE];
xValues[n]=p.x;
yValues[n]=p.y; // update coordinates in arrays
[self drawKnobsFrom:drawFrom // draw new knobs and segments
to:drawTo in:LTGRAY:TRANSP];
[self drawSegmentsFrom:drawFrom
to:drawTo in:envColour];
[[self window] flushWindow];
return n;
}
//-------------------------------------------------------------------
// addPoint: -- Adds a point to the envelope
// Returns number of new point, -1 on error
- (int) addPoint:(NXPoint)p
{
int point, newp, oldp, newpc, newSticky;
if (p.x<xMin || p.x>=xMax || // check point is within bounds
p.y<yMin || p.y>yMax)
return -1;
if (ySnap!=0) p.y=rint(p.y/ySnap)*ySnap; // snap into y grid
if ((point=[self insertPointAt:p])<0) // and that we get a valid point number
return -1;
newpc=pointCount+1;
[self allocateTemp:newpc];
[self allocateDraw:newpc];
draw->num=0; // clear erase path
newSticky=[theEnvelope stickPoint];
if (newSticky!=MAXINT && stickyPoint>point)
newSticky++; // adjust value of stick point
for (newp=oldp=0; newp<newpc; newp++) {
if (newp==point) {
temp->x[newp]=p.x; // set values of new point
temp->y[newp]=p.y;
draw->yr[newp]=p.y;
temp->s[newp]=defaultSmooth;
}
else {
temp->x[newp]=xValues[oldp]; // copy old values into new arrays
temp->y[newp]=yValues[oldp];
draw->yr[newp]=draw->yr[oldp];
if (sValues!=NULL)
temp->s[newp]=sValues[oldp];
else
temp->s[newp]=defaultSmooth;
oldp++;
}
}
[theEnvelope // redefine the old envelope object
setPointCount: newpc
xArray: temp->x
orSamplingPeriod: 1.0
yArray: temp->y
smoothingArray: temp->s
orDefaultSmoothing: defaultSmooth];
[theEnvelope setStickPoint: newSticky];
return point;
}
//-------------------------------------------------------------------
// removePoint: -- Remove a point from the envelope
// Returns the new number of points or -1 on error
- (int) removePoint:(int)n
{
int oldp, newp, newpc, newSticky;
if (pointCount<3) return -1; // leave always at least two breaks
newpc=pointCount-1;
[self allocateTemp:newpc];
[self allocateDraw:newpc];
draw->num=0;
for (oldp=newp=0; newp<newpc; newp++,oldp++) {
if (oldp==n) oldp++; // copy arrays skipping deleted element
temp->x[newp]=xValues[oldp];
temp->y[newp]=yValues[oldp];
draw->yr[newp]=draw->yr[oldp];
if (sValues!=NULL)
temp->s[newp]=sValues[oldp];
}
newSticky=[theEnvelope stickPoint];
if (newSticky!=MAXINT) {
if (newSticky==n)
newSticky=MAXINT; // remove stick point with point
else if (newSticky>n)
newSticky--; // or adjust value if higher
}
[theEnvelope
setPointCount: newpc
xArray: temp->x
orSamplingPeriod: 1.0
yArray: temp->y
smoothingArray: temp->s
orDefaultSmoothing: defaultSmooth];
[theEnvelope setStickPoint: newSticky]; // update envelope object
[self selectPoint: n-1]; // and select previous point
return pointCount;
}
//-------------------------------------------------------------------
// mouseDown: -- Responds to a mousedown event
// The following is the behaviour of the mouse:
// hit a knob: --> drag the point
// hit with shift: --> delete envelope breakpoint
// hit with alternate: --> toggle sticky point at breakpoint
// no hit with shift: --> create a new envelope breakpoint
- mouseDown:(NXEvent *)event
{
NXPoint ep, p;
int hitpt; // Point to move/remove
int oldMask;
ep=event->location;
[self convertPoint:&ep fromView:nil];
p.x=pixToX(ep.x);
p.y=pixToY(ep.y); // convert from pixels to x/y
hitpt=[self hitKnobAt: p border: KNOBSIZE/2]; // see if it is a breakpoint
if (event->flags & NX_SHIFTMASK) { // with shift key down...
if (hitpt>=0) {
if ([self removePoint: hitpt] > 0) // hit --> remove point
hitpt=-1;
}
else hitpt=[self addPoint: p]; // no hit --> add point
}
if ((event->flags&NX_ALTERNATEMASK)&&(hitpt>=0)) { // hit plus alternate...
if (stickyPoint==hitpt)
[theEnvelope setStickPoint: MAXINT];
else
[theEnvelope setStickPoint: hitpt];
[self selectPoint: hitpt];
hitpt=-1;
}
if (hitpt >= 0) { // Move hitpt as mouse drags
[self selectPoint: hitpt]; // select and redraw image
oldMask = [[self window] addToEventMask:DRAG_MASK];
[self lockFocus];
[theFilledCross push]; // use cursor = cross+knob
[self eraseSelectedKnob];
while (event->type != NX_MOUSEUP) {
ep=event->location;
[self convertPoint:&ep fromView:nil];
if ([NXApp peekNextEvent: DRAG_MASK into: event]==NULL) {
p.x=pixToX(ep.x);
p.y=pixToY(ep.y);
[self movePoint:hitpt to:p];
[theController updateCoords: self at: hitpt];
NXPing();
}
event = [NXApp getNextEvent:DRAG_MASK];
}
[NXCursor pop]; // return to crosshair cursor
[self unlockFocus];
}
[self display];
return self;
}
//===================================================================
// Messages received from window to change first responder status
//===================================================================
//-------------------------------------------------------------------
// highlight
- highlight
{
envColour=BLACK;
[self display];
return self;
}
//-------------------------------------------------------------------
// dim
- dim
{
envColour=DKGRAY;
[self display];
return self;
}
//-------------------------------------------------------------------
// acceptsFirstResponder:
- (BOOL) acceptsFirstResponder
{
return YES;
}
//-------------------------------------------------------------------
// becomeFirstResponder:
- becomeFirstResponder
{
if (theController!=NULL)
[theController update: self];
envColour=BLACK;
[self display];
return self;
}
//-------------------------------------------------------------------
// resignFirstResponder:
- resignFirstResponder
{
return self;
}
//===================================================================
// Pasteboard interface methods
//===================================================================
//-------------------------------------------------------------------
// copy: -- Copy the current envelope to the pasteboard
- copy:sender
{
int point; // current point
char select; // number representing the style
const char *style; // string representing the style
const char *types[1];
int num=0;
char *s; // output string
int max; // max number of chars in the output string
char tmp[128]; // temporary buffer for conversions
max=1024;
s=NXZoneMalloc([self zone], max*sizeof(char)); // allocate array for output string
if (theController==NULL) // get current selected style
style="MusicKit";
else {
style=[theController getStyle];
if (style==NULL) style="MusicKit";
}
select=STYLE_MK;
if (strcmp(style,"MusicKit")==0) select=STYLE_MK;
if (strcmp(style,"(x y ...)")==0) select=STYLE_pX_Yp;
if (strcmp(style,"(x,y ...)")==0) select=STYLE_pXcYp;
if (strcmp(style,"x y ...")==0) select=STYLE_X_Y;
if (strcmp(style,"x,y ...")==0) select=STYLE_XcY;
if (strcmp(style,"x,y,z ...")==0) select=STYLE_XcYcZ_;
if (strcmp(style,"x y z,...")==0) select=STYLE_X_Y_Zc;
if (strcmp(style,"((x y)...)")==0) select=STYLE_ppX_Yp;
switch(select)
{
case STYLE_MK:
strcpy(s,"[");
for (point=0; point<pointCount-1; point++) {
sprintf(tmp,"(%5.3f,%5.3f",xValues[point],yValues[point]);
strcat(s,tmp);
if (sValues[point]!=defaultSmooth) {
sprintf(tmp,",%5.3f",sValues[point]);
strcat(s,tmp);
}
strcat(s,")");
if (stickyPoint==point) strcat(s,"|");
if (strlen(s)>max-128) {
max+=1024;
s=NXZoneRealloc([self zone], s,max*sizeof(char));
}
}
sprintf(tmp,"(%5.3f,%5.3f",xValues[pointCount-1],yValues[pointCount-1]);
strcat(s,tmp);
if (sValues[point]!=defaultSmooth) {
sprintf(tmp,",%5.3f",sValues[point]);
strcat(s,tmp);
}
strcat(s,")");
if (stickyPoint==point) strcat(s,"|");
strcat(s,"]");
break;
case STYLE_pX_Yp:
strcpy(s,"(");
for (point=0; point<pointCount-1; point++) {
sprintf(tmp,"%5.3f %5.3f ",xValues[point],yValues[point]);
strcat(s,tmp);
if (strlen(s)>max-128) {
max+=1024;
s=NXZoneRealloc([self zone], s,max*sizeof(char));
}
}
sprintf(tmp,"%5.3f %5.3f)",xValues[pointCount-1],yValues[pointCount-1]);
strcat(s,tmp);
break;
case STYLE_pXcYp:
strcpy(s,"(");
for (point=0; point<pointCount-1; point++) {
sprintf(tmp,"%5.3f,%5.3f,",xValues[point],yValues[point]);
strcat(s,tmp);
if (strlen(s)>max-128) {
max+=1024;
s=NXZoneRealloc([self zone], s,max*sizeof(char));
}
}
sprintf(tmp,"%5.3f,%5.3f)",xValues[pointCount-1],yValues[pointCount-1]);
strcat(s,tmp);
break;
case STYLE_X_Y:
strcpy(s,"");
for (point=0; point<pointCount-1; point++) {
sprintf(tmp,"%5.3f %5.3f ",xValues[point],yValues[point]);
strcat(s,tmp);
if (strlen(s)>max-128) {
max+=1024;
s=NXZoneRealloc([self zone], s,max*sizeof(char));
}
}
sprintf(tmp,"%5.3f %5.3f",xValues[pointCount-1],yValues[pointCount-1]);
strcat(s,tmp);
break;
case STYLE_XcY:
strcpy(s,"");
for (point=0; point<pointCount-1; point++) {
sprintf(tmp,"%5.3f,%5.3f,",xValues[point],yValues[point]);
strcat(s,tmp);
if (strlen(s)>max-128) {
max+=1024;
s=NXZoneRealloc([self zone], s,max*sizeof(char));
}
}
sprintf(tmp,"%5.3f,%5.3f",xValues[pointCount-1],yValues[pointCount-1]);
strcat(s,tmp);
break;
case STYLE_XcYcZ_:
strcpy(s,"");
for (point=0; point<pointCount-1; point++) {
sprintf(tmp,"%5.3f,%5.3f,%5.3f ",xValues[point],yValues[point],sValues[point]);
strcat(s,tmp);
if (strlen(s)>max-128) {
max+=1024;
s=NXZoneRealloc([self zone], s,max*sizeof(char));
}
}
sprintf(tmp,"%5.3f,%5.3f,%5.3f",xValues[pointCount-1],yValues[pointCount-1],sValues[pointCount-1]);
strcat(s,tmp);
break;
case STYLE_X_Y_Zc:
strcpy(s,"");
for (point=0; point<pointCount-1; point++) {
sprintf(tmp,"%5.3f %5.3f %5.3f,",xValues[point],yValues[point],sValues[point]);
strcat(s,tmp);
if (strlen(s)>max-128) {
max+=1024;
s=NXZoneRealloc([self zone], s,max*sizeof(char));
}
}
sprintf(tmp,"%5.3f %5.3f %5.3f",xValues[pointCount-1],yValues[pointCount-1],sValues[pointCount-1]);
strcat(s,tmp);
break;
case STYLE_ppX_Yp:
strcpy(s,"(");
for (point=0; point<pointCount-1; point++) {
sprintf(tmp,"(%5.3f %5.3f)",xValues[point],yValues[point]);
strcat(s,tmp);
if (strlen(s)>max-128) {
max+=1024;
s=NXZoneRealloc([self zone], s,max*sizeof(char));
}
}
sprintf(tmp,"(%5.3f %5.3f))",xValues[pointCount-1],yValues[pointCount-1]);
strcat(s,tmp);
break;
}
types[num++] = NXAsciiPboardType;
[[Pasteboard new] declareTypes: types num: num owner: [self class]];
[[Pasteboard new] writeType: NXAsciiPboardType data:s length:strlen(s)];
NXZoneFree([self zone], s);
return self;
}
//-------------------------------------------------------------------
// paste: -- Paste the current pasteboard contents into the view. The
// method parses the text representation of the envelope automatically
// deciding on the type of envelope received. The internal representation
// is a standard MusicKit envelope object.
// Get next token from a string (symbol or number)
#define NUMBER 0
char *data;
// Parse the data stream in tokens representing symbols and numbers
int token(char *t)
{
char c;
int i;
while (((t[0]=c=*data++) == ' ')||(c == '\t')||(c == '\n'));
t[1]='\0';
if (c=='\0') {
data--;
return EOF;
}
if (!isdigit(c) && c!= '.' && c!= '-')
return c;
i=0; // collect a number stream
if (c=='-')
t[++i]=c=*data++;
if (isdigit(c))
while (isdigit(t[++i]=c=*data++));
if (c == '.')
while (isdigit(t[++i]=c=*data++));
if ((c == 'e')||(c == 'E')) {
t[++i]=c=*data++;
if (c=='-')
t[++i]=c=*data++;
if (isdigit(c))
while (isdigit(t[++i]=c=*data++));
}
t[i]='\0';
if (c!='\0')
data--;
return NUMBER;
}
- paste:sender
{
char *prs, *orig, tk[1024];
int symb, length;
const NXAtom *pastetypes;
int sticky, point;
pastetypes = [[Pasteboard new] types];
if ([[Pasteboard new]
readType: NXAsciiPboardType
data:&prs
length:&length]) { // if ASCII in pasteboard...
data=orig=NXZoneCalloc([self zone], length+16,sizeof(char)); // copy data to local buffer
strncpy(data,prs,length);
point=0; // start by converting point 0
sticky=MAXINT; // no sticky point by default
[self allocateTemp:64]; // 64 points long by default
if ((symb=token(tk))=='(') {
if (symb=token(tk)=='(') {
// parse a list of lists type envelope
// accepted syntax: "((x0 y0)...(xn yn))" or "((x0,y0)...(xn,yn))"
while((symb!=')')&&(symb!=-1)) {
symb=token(tk); // should be "x" component
if (symb!=NUMBER) { // must be x component
NXRunAlertPanel("Error",
"Expected x component at:\n\"%s\"\nin:\n\"%s\"",
NULL,NULL,"Continue",data-1,orig);
break;
}
else {
temp->x[point]=atof(tk); // convert "x" component!
temp->y[point]=0.0; // and set defaults
temp->s[point]=defaultSmooth;
}
symb=token(tk);
if (symb!=NUMBER) {
NXRunAlertPanel("Error",
"Expected y component at:\n\"%s\"\nin:\n\"%s\"",
NULL,NULL,"Continue",data-1,orig);
break;
}
else {
temp->y[point]=atof(tk); // convert "y" component
point++; // count a complete envelope node
[self allocateTemp:point+1];
}
if (symb=token(tk)!=')') {
NXRunAlertPanel("Error",
"Expected closing parenthesis at:\n\"%s\"\nin:\n\"%s\"",
NULL,NULL,"Continue",data-1,orig);
break;
}
else symb=token(tk);
}
showSmooth=0; // only MK shows smoothing by default
}
else {
// parse a CLM type envelope
// accepted syntax: "(x0 y0 ... xn yn)" or "(x0,y0, ... xn,yn)"
while((symb!=')')&&(symb!=-1)) {
if (symb==',') symb=token(tk); // ignore commas between xy pairs
if (symb!=NUMBER) { // must be x component
NXRunAlertPanel("Error",
"Expected x component at:\n\"%s\"\nin:\n\"%s\"",
NULL,NULL,"Continue",data-1,orig);
break;
}
else {
temp->x[point]=atof(tk); // convert "x" component!
temp->y[point]=0.0; // and set defaults
temp->s[point]=defaultSmooth;
}
if ((symb=token(tk))==',') // ignore commas between values
symb=token(tk);
if (symb!=NUMBER) {
NXRunAlertPanel("Error",
"Expected y component at:\n\"%s\"\nin:\n\"%s\"",
NULL,NULL,"Continue",data-1,orig);
break;
}
else {
temp->y[point]=atof(tk); // convert "y" component
point++; // count a complete envelope node
[self allocateTemp:point+1];
}
symb=token(tk);
}
showSmooth=0; // only MK shows smoothing by default
}
}
else if (symb=='[') {
// parse a MusicKit envelope
// uses normal MusicKit syntax
symb=token(tk); // should be starting '(' or '|'
while((symb=='(')||(symb=='|')) {
if (symb=='|') {
sticky=point-1; // last point was the sticky point
symb=token(tk); // should be '(' or the end
continue;
}
if ((symb=token(tk))!=NUMBER) { // break if not a number
NXRunAlertPanel("Error",
"Expected x component at:\n\"%s\"\nin:\n\"%s\"",
NULL,NULL,"Continue",data-1,orig);
break;
}
else {
temp->x[point]=atof(tk); // convert "x" component!
temp->y[point]=0.0;
temp->s[point]=defaultSmooth;
}
if ((symb=token(tk))==',') // skip comma but also accept a space
symb=token(tk);
if (symb!=NUMBER) { // break if not a number
NXRunAlertPanel("Error",
"Expected y component at:\n\"%s\"\nin:\n\"%s\"",
NULL,NULL,"Continue",data-1,orig);
break;
}
else {
temp->y[point]=atof(tk); // convert "y" component
}
if ((symb=token(tk))==',') // is there a smoothing component?
if ((symb=token(tk))==NUMBER) { // if number...
temp->s[point]=atof(tk); // convert smoothing component
symb=token(tk);
}
point++; // count a complete envelope node
[self allocateTemp:point+1];
if (symb!=')') { // must be point's closing parenthesis
NXRunAlertPanel("Error",
"Expected a ')' at:\n\"%s\"\nin:\n\"%s\"",
NULL,NULL,"Continue",data-1,orig);
break;
}
symb=token(tk);
}
showSmooth=-1;
}
else if (symb==NUMBER) {
// parse a x-y-<z> pair type envelope
// accepted syntax: "x0 y0 ... xn yn" or "x0,y0, ... xn,yn" for xy pairs
// accepted syntax: "x0,y0,z0 x1,y1,z1 ..." or " x0 y0 z0,x1 y1 z1, ..." for xyz sets
int commas=0;
double last_value=0.0;
int pending=0;
while(symb!=-1) {
if (pending==0) {
if (symb!=NUMBER) { // not a number!
NXRunAlertPanel("Error",
"Expected x component at:\n\"%s\"\nin:\n\"%s\"",
NULL,NULL,"Continue",data-1,orig);
break;
}
else {
temp->x[point]=atof(tk); // convert "x" component!
temp->y[point]=0;
temp->s[point]=defaultSmooth;
}
if ((symb=token(tk))==',') {
commas++;
symb=token(tk);
}
}
else {
temp->x[point]=last_value;
temp->y[point]=0.0;
temp->s[point]=defaultSmooth;
pending=0;
}
if (symb!=NUMBER) {
NXRunAlertPanel("Error",
"Expected y component at:\n\"%s\"\nin:\n\"%s\"",
NULL,NULL,"Continue",data-1,orig);
break;
}
else temp->y[point]=atof(tk); // convert "y" component
if ((symb=token(tk))==',') {
commas++;
symb=token(tk);
}
if (symb==-1) {
point++;
[self allocateTemp:point+1];
break;
}
else {
if (symb!=NUMBER) {
NXRunAlertPanel("Error",
"Expected x or z component at:\n\"%s\"\nin:\n\"%s\"",
NULL,NULL,"Continue",data-1,orig);
break;
}
else last_value=atof(tk);
if ((((symb=token(tk))!=',')&&(commas==2)) ||
((symb==',')&&(commas==0))) {
temp->s[point]=last_value;
pending=0;
commas=0;
}
else {
pending=1;
commas=1;
}
point++;
[self allocateTemp:point+1];
}
if (symb==',') symb=token(tk);
}
showSmooth=0; // only MK shows smoothing by default
}
else
NXRunAlertPanel(
"Error",
"The envelope must start with '[','(' or a number:\n\"%s\"",
NULL,NULL,"Continue",orig);
vm_deallocate(task_self(),(vm_address_t)prs, length);
if (point<2) { // if envelope has less than 2 nodes
NXRunAlertPanel(
"Error",
"Less than 2 legal points in envelope:\n\"%s\"",
NULL,NULL,"Continue",orig);
}
else {
[theEnvelope
setPointCount: point
xArray: temp->x
orSamplingPeriod: 1.0
yArray: temp->y
smoothingArray: temp->s
orDefaultSmoothing: defaultSmooth];
[theEnvelope setStickPoint: sticky]; // update envelope object
[self allocateDraw:temp->max]; //(pointCount); // resize drawing arrays
[self scaleLimits]; // define drawing limits
}
if (selected<pointCount)
[self selectPoint: selected];
else
[self selectPoint: 0]; // display and update controller
NXZoneFree([self zone], orig); // free local copy of data
}
return self;
}
//===================================================================
// Messages received from controller to change parameters
//===================================================================
//-------------------------------------------------------------------
// setPointTo: set current point to n (as a side effect updates values
// of x and y coordinates on the controller object
- setPointTo: (int)point
{
if (point>=pointCount) point=pointCount-1;
if (point<0) point=0;
[self selectPoint: point];
[theController update: self];
return self;
}
//-------------------------------------------------------------------
// nextPoint go to the next point in the envelope if possible
- nextPoint
{
int next;
next=selected+1;
if (next>=pointCount) next=selected;
if (next<0) next=0;
if (next!=selected) {
[self selectPoint:next];
[theController update: self];
}
return self;
}
//-------------------------------------------------------------------
// previousPoint go to the previous point in the envelope if possible
- previousPoint
{
int previous;
previous=selected-1;
if (previous>=pointCount) previous=selected;
if (previous<0) previous=0;
if (previous!=selected) {
[self selectPoint:previous];
[theController update: self];
}
return self;
}
//-------------------------------------------------------------------
// setXAt:to: changes value of x coordinate of point n
- setXAt: (int)n to: (float)coord
{
if (n!=0 && coord<xValues[n-1]) // force selected point to be within
coord=xValues[n-1]; // enclosing points
if (n!=pointCount-1 && coord>xValues[n+1])
coord=xValues[n+1];
if (coord!=xValues[n]) { // if x changed update display panel
xValues[n]=coord;
[self display];
[theController update: self];
}
return self;
}
//-------------------------------------------------------------------
// setYAt:to: changes value of y coordinate of point n
- setYAt: (int)n to: (float)coord
{
if (coord>yMax) coord=yMax; // clip y values to max and min
if (coord<yMin) coord=yMin;
if (coord!=yValues[n]) { // if y changed update display panel
yValues[n]=coord;
[self display];
[theController update: self];
}
return self;
}
//-------------------------------------------------------------------
// setYrAt:to: changes value of real y coordinate of point n
- setYrAt: (int)n to: (float)coord
{
double y;
if (coord>yMax) coord=yMax; // clip y values to max and min
if (coord<yMin) coord=yMin;
if (n==0) return self; // no sense to change this in first point
draw->yr[n]=coord;
y=(coord-yValues[n-1]*exp(-5.5262/sValues[n]))/(1-exp(-5.5262/sValues[n]));
[self setYAt: n to: y];
return self;
}
//-------------------------------------------------------------------
// setSmoothAt:to: changes value of smoothing of point n
- setSmoothAt: (int)n to: (float)value
{
if (value!=sValues[n]) { // if smoothing changed update panel
sValues[n]=value;
[self display];
[theController update: self];
}
return self;
}
//-------------------------------------------------------------------
// setXMinTo: changes minimum value of x component of envelope
- setXMinTo: (float)coord
{
if (coord<xMax) {
xMin=coord;
[self display];
}
else if (theController!=NULL)
[theController update: self];
return self;
}
//-------------------------------------------------------------------
// setXMaxTo: changes maximun value of x component of envelope
- setXMaxTo: (float)coord
{
if (coord>xMin) {
xMax=coord;
[self display];
}
else if (theController!=NULL)
[theController update: self];
return self;
}
//-------------------------------------------------------------------
// setXLimitsTo:: changes max and min values of x component
- setXLimitsTo: (float)min : (float)max
{
xMin=min;
xMax=max;
[self display];
return self;
}
//-------------------------------------------------------------------
// setYMinTo: changes minimum value of y component of envelope
- setYMinTo: (float)coord
{
if (coord<yMax) {
yMin=coord;
[self display];
}
else if (theController!=NULL)
[theController update: self];
return self;
}
//-------------------------------------------------------------------
// setYMaxTo: changes maximun value of y component of envelope
- setYMaxTo: (float)coord
{
if (coord>yMax) {
yMax=coord;
[self display];
}
else if (theController!=NULL)
[theController update: self];
return self;
}
//-------------------------------------------------------------------
// setXSnapTo: changes value of x Snap
- setXSnapTo: (float)coord
{
xSnap=coord;
[self display];
return self;
}
//-------------------------------------------------------------------
// setYSnapTo: changes value of y Snap
- setYSnapTo: (float)coord
{
ySnap=coord;
[self display];
return self;
}
//-------------------------------------------------------------------
// setStickyAt:To: sets point to be the sticky point of envelope
- setStickyAt:(int) point To: (int)state
{
if (state==0)
[theEnvelope setStickPoint: MAXINT];
else
[theEnvelope setStickPoint: point];
[theController update: self];
[self display];
return self;
}
//-------------------------------------------------------------------
// setShowSmooth: sets type of graphics to be used
- setShowSmooth: (int)state
{
showSmooth=state;
[self display];
[theController update: self];
return self;
}
//-------------------------------------------------------------------
// setDrawSegments: choose to draw segments or only points
- setDrawSegments: (int)state
{
drawSegments=state;
[self display];
[theController update: self];
return self;
}
//-------------------------------------------------------------------
// scaleLimits computes the max and min bounds of the view
- scaleLimits
{
double xmin, xmax, ymin, ymax;
int point;
xmin=xmax=xValues[0];
ymin=ymax=yValues[0];
for (point=1; point<pointCount; point++) {
if (xValues[point]<xmin) xmin=xValues[point];
if (xValues[point]>xmax) xmax=xValues[point];
if (yValues[point]<ymin) ymin=yValues[point];
if (yValues[point]>ymax) ymax=yValues[point];
}
xMax=xmax;
xMin=xmin;
if (ymax>=0.0 && ymax<=10.0)
yMax=rint(ymax*10+1.0)/10;
else
yMax=rint(ymax+1.0);
if (ymin<=0.0 && ymin>=-1.0)
yMin=rint(ymin*10-1.0)/10;
else
yMin=rint(ymin-1.0);
if (ymin==0) yMin=ymin;
[self display];
return self;
}
//===================================================================
// Messages received from controller to query for envelope values
//===================================================================
//-------------------------------------------------------------------
// (int)getPoint Return the selected point
- (int)getPoint
{
return selected;
}
//-------------------------------------------------------------------
// (float)getX:(int)i Return value of x component of point i
- (float)getX:(int)i
{
if (i>=pointCount) i=pointCount-1;
if (i<0) i=0;
return xValues[i];
}
//-------------------------------------------------------------------
// (float)getY:(int)i Return value of y component of point i
- (float)getY:(int)i
{
if (i>=pointCount) i=pointCount-1;
if (i<0) i=0;
return yValues[i];
}
//-------------------------------------------------------------------
// (float)getYr:(int)i Return value of real y component of point i
- (float)getYr:(int)i
{
if (i>=pointCount) i=pointCount-1;
if (i<0) i=0;
return draw->yr[i];
}
//-------------------------------------------------------------------
// (float)getSmoothing:(int)i Return value of smoothing of point i
- (float)getSmoothing:(int)i
{
if (i>=pointCount) i=pointCount-1;
if (i<0) i=0;
return sValues[i];
}
//-------------------------------------------------------------------
// (int)getSticky:(int)i Return value of stickyness of point i
- (int)getSticky:(int)i
{
if (i>=pointCount) i=pointCount-1;
if (i<0) i=0;
if (stickyPoint==i)
return 1;
else
return 0;
}
//-------------------------------------------------------------------
// (float)getXMax
- (float) getXMax { return xMax; }
//-------------------------------------------------------------------
// (float)getXMin
- (float) getXMin { return xMin; }
//-------------------------------------------------------------------
// (float)getYMax
- (float) getYMax { return yMax; }
//-------------------------------------------------------------------
// (float)getYMin
- (float) getYMin { return yMin; }
//-------------------------------------------------------------------
// (float)getXSnap
- (float) getXSnap { return xSnap; }
//-------------------------------------------------------------------
// (float)getYSnap
- (float) getYSnap { return ySnap; }
//-------------------------------------------------------------------
// (int)getShowSmooth
- (int)getShowSmooth
{
return showSmooth;
}
//-------------------------------------------------------------------
// (int)getDrawSegments
- (int)getDrawSegments
{
return drawSegments;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.