old quark gui: openOS is not osx only
[supercollider.git] / editors / scapp / SCSoundFileView.M
blobf156001f0c1251f9e08ed20722069017f0fa6e98
1 /*
2 * SCSoundFileView.cpp
3 * xSC3lang
5 * Created by falkenst on Thu Nov 18 2004.
6 * Copyright (c) 2004 jan truetzschler. All rights reserved.
8 SuperCollider real time audio synthesis system
9 Copyright (c) 2002 James McCartney. All rights reserved.
10 http://www.audiosynth.com
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 2 of the License, or
15 (at your option) any later version.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
28 #include <Cocoa/Cocoa.h>
29 #include <Carbon/Carbon.h>
30 #include <pthread.h>
31 #include "SCBase.h"
32 #include "PyrSymbol.h"
33 #include "PyrPrimitive.h"
34 #include "PyrObject.h"
35 #include "PyrKernel.h"
36 #include "GC.h"
37 #include "VMGlobals.h"
38 #include "SC_RGen.h"
39 #include "SC_BoundsMacros.h"
40 #include "SC_InlineBinaryOp.h"
43 #include "SCSoundFileView.h"
45 int slotColorVal(PyrSlot *slot, SCColor *sccolor);
46 int setSlotColor(PyrSlot *slot, SCColor *sccolor);
47 CGRect SCtoCGRect(SCRect screct);
48 extern PyrSymbol* s_x;
49 extern PyrSymbol* s_y;
50 void QDDrawBevelRect(CGContextRef cgc, CGRect bounds, float width, bool inout);
51 int allocSlotStrVal(PyrSlot *slot, char **str);
52 int slotGetSCRect(PyrSlot* a, SCRect *r);
56 // replacement for calloc.
57 // calloc lazily zeroes memory on first touch. This is good for most purposes, but bad for realtime audio.
58 void *zalloc(size_t n, size_t size)
60 size *= n;
61 if (size) {
62 void* ptr = malloc(size);
63 if (ptr) {
64 memset(ptr, 0, size);
65 return ptr;
68 return 0;
71 inline int32 BUFMASK(int32 x)
73 return (1 << (31 - CLZ(x))) - 1;
76 int bufAlloc(SndBuf* buf, int numChannels, int numFrames, double sampleRate);
77 int bufAlloc(SndBuf* buf, int numChannels, int numFrames, double sampleRate)
79 long numSamples = numFrames * numChannels;
80 if(numSamples < 1) return errFailed;
81 if(buf->data) free(buf->data);
82 buf->data = (float*)zalloc(numSamples, sizeof(float));
83 if (!buf->data) return errFailed;
85 buf->channels = numChannels;
86 buf->frames = numFrames;
87 buf->samples = numSamples;
88 buf->mask = BUFMASK(numSamples); // for delay lines
89 buf->mask1 = buf->mask - 1; // for oscillators
90 buf->samplerate = sampleRate;
91 buf->sampledur = 1. / sampleRate;
93 return errNone;
96 int bufAllocMinMax(SndMinMaxBuf* buf, int numChannels, int numFrames);
97 int bufAllocMinMax(SndMinMaxBuf* buf, int numChannels, int numFrames)
99 long numSamples = numFrames * numChannels;
100 if(numSamples < 1) return errFailed;
101 buf->isUsable = false;
102 if(buf->min) free(buf->min);
103 if(buf->max) free(buf->max);
104 buf->min = (float*)zalloc(numSamples, sizeof(float));
105 buf->max = (float*)zalloc(numSamples, sizeof(float));
106 if (!buf->min || !buf->max) return errFailed;
107 buf->samples = numSamples;
109 return errNone;
112 SCView* NewSCSoundFileView(SCContainerView *inParent, PyrObject* inObj, SCRect inBounds)
114 return new SCSoundFileView(inParent, inObj, inBounds);
117 SCSoundFileView::SCSoundFileView(SCContainerView *inParent, PyrObject* inObj, SCRect inBounds)
118 : SCView(inParent, inObj, inBounds), mBufNum(0), mStyle(0), mGridOn(true), mGridResolution(1.f), mGridOffset(0), mIsReadingSoundFile(false),
119 mCurrentSelection(0), mMoveSelection(false), mLastFrame(0), mDrawsWaveForm(true), mShowTimeCursor (false), mFramesInBounds(0)
121 mZoom = mInvZoom = SCMakePoint(1.,1.);
122 mScroll = SCMakePoint(0.,0.);
123 memset(&mSndFile, 0, sizeof(SndBuf));
124 memset(&mSndMinMax, 0, sizeof(SndMinMaxBuf));
125 mSndMinMax.isUsable = false;
126 mSndBuf.frames = 0;
127 mSndBuf.data = 0;
128 mSndBuf.channels = 0;
129 mSndBuf.samplerate = 44100;
130 mResampleFactor = 64;
131 mGridColor = SCMakeColor(0.2,0.2,1.0, 1.0);
132 mBackground = new SolidColorBackground(
133 SCMakeColor(0.,0.,0., 1.0));
134 SCColor waveColor = SCMakeColor(1.0,1.0,0.0, 1.0);
135 mTimeCursorPosition = 0;
136 mTimeCursorColor = SCMakeColor(0.1,0.2,1.0, 1.0);
137 mElasticMode = 0;
139 for (int i=0; i<kMaxSndSelections; ++i)
141 mSelections[i].startIsEditable = true;
142 mSelections[i].sizeIsEditable = true;
143 mSelections[i].size = 0;
144 mSelections[i].start = 0;
145 mSelections[i].color = SCMakeColor(0.2,0.2,1.0, 0.4);
147 for (int i=0; i<kMaxSndChannels; ++i) mWaveColors[i] = waveColor;
150 SCSoundFileView::~SCSoundFileView()
152 free(mSndBuf.data);
153 free(mSndMinMax.min);
154 free(mSndMinMax.max);
159 int SCSoundFileView::readSndFile(SNDFILE *file, int startFrame, int frames, int resampleFactor, SF_INFO info)
162 if (!file || mIsReadingSoundFile) return errFailed;
163 mIsReadingSoundFile = true;
165 //check for
166 startFrame = sc_min(startFrame, info.frames - 1);
167 int maxframes = info.frames - startFrame;
168 if(!frames || frames > maxframes) frames = maxframes;
169 //would be good to find a way to calculate an optimal factor ...
170 if(!resampleFactor) resampleFactor=64;
171 mResampleFactor = (float) resampleFactor;
172 mResampleFactor = sc_clip(mResampleFactor, 1.f, maxframes);
173 mScroll.x = 0.;
174 int readsize = (int) mResampleFactor;
176 // post("zoom.x: %f, frames: %d, resampleFactor: %f \n", mZoom.x, (int) frames, mResampleFactor);
178 int err = bufAlloc(&mSndBuf, info.channels, (int) (frames/mResampleFactor), info.samplerate);
179 mZoom.x = ((frames/mResampleFactor)/(mBounds.width-2.f));
181 if(err) return err;
182 int channels = sc_min(mSndBuf.channels, kMaxSndChannels);
184 if(mSndMinMax.isUsable) {
185 mSndMinMax.isUsable=false;
186 free(mSndMinMax.min);
187 free(mSndMinMax.max);
189 bufAllocMinMax(&mSndMinMax, channels, (int)(mBounds.width ));
192 if(startFrame>0) sf_seek(file, startFrame, SEEK_SET);
193 if(mResampleFactor>1){
195 float data[readsize*channels];
196 int count=0;
197 for(int i=0; i<frames; i+=readsize){
198 sf_read_float(file, data, readsize*channels);
199 // find minmax: ...
200 for (int j=0; j<channels; ++j)
202 float ymin, ymax, val;
203 val = data[j];
204 ymin = ymax = val;
205 //read through all samples
206 for(int k=0; k<(readsize*channels); k+=channels)
208 val = data[k+j];
209 if(val<ymin) ymin = val;
210 else if(val>ymax) ymax = val;
213 if(sc_abs(ymin) > sc_abs(ymax)) mSndBuf.data[count++] = ymin;
214 else mSndBuf.data[count++] = ymax;
218 } else {
219 sf_read_float(file, mSndBuf.data, frames*channels);
221 mIsReadingSoundFile = false;
222 return errNone;
225 int SCSoundFileView::findSelection(int frame)
227 for (int i=0; i<kMaxSndSelections; ++i)
229 if(frame >= mSelections[i].start && frame < mSelections[i].start+mSelections[i].size)
230 return i;
232 return -1;
235 void SCSoundFileView::setBounds(SCRect inBounds)
237 if(inBounds.width != mBounds.width){
238 if(mElasticMode) mZoom.x = mSndBuf.frames/(inBounds.width-2.f);
239 mSndMinMax.isUsable = false;
240 if(mSndBuf.channels)
241 bufAllocMinMax(&mSndMinMax, mSndBuf.channels, (int)(inBounds.width ));
243 mBounds = inBounds;
244 if(!(mParent->isSubViewScroller())){
245 SCRect pbounds = mParent->getLayout().bounds;
246 mLayout.bounds.x = mBounds.x + pbounds.x;
247 mLayout.bounds.y = mBounds.y + pbounds.y;
248 mLayout.bounds.width = mBounds.width;
249 mLayout.bounds.height = mBounds.height;
250 } else {
251 mLayout.bounds = mBounds;
255 void SCSoundFileView::mouseBeginTrack(SCPoint where, int modifiers, NSEvent *theEvent)
257 //mPrevPoint = where;
258 //mouseTrack(where, modifiers,theEvent);
259 int frame;
260 [[NSCursor resizeLeftCursor] set];
261 SCRect bounds = getDrawBounds();
262 frame = (int) (mScroll.x + ((where.x - bounds.x - 1) * mZoom.x * mResampleFactor ));
263 //post("current frame is %d, bounds.x is %d\n", frame, mBounds.x);
264 if (modifiers & NSAlternateKeyMask) {
265 // refresh();
266 } else {
267 //check if there is a selection:
268 int selection = findSelection(frame);
269 if(selection >= 0){
270 mMoveSelection = true;
271 mCurrentSelection = selection;
272 [[NSCursor openHandCursor] set];
274 } else {
275 mMoveSelection = false;
276 if(mSelections[mCurrentSelection].startIsEditable) mSelections[mCurrentSelection].start = frame;
279 mTimeCursorPosition = frame;
280 mLastFrame = frame;
284 void SCSoundFileView::mouseTrack(SCPoint where, int modifiers, NSEvent *theEvent)
286 SCRect bounds = getDrawBounds();
287 mAbsolutePosition.x = (int) where.x; //absolute mouse position
288 mAbsolutePosition.y = (int) where.y;
289 int frame;
290 frame = (int) (mScroll.x + (where.x - bounds.x - 1) * mZoom.x * mResampleFactor );
291 if (modifiers & NSAlternateKeyMask) {
292 SCSoundFileSelection * currentSelection = &mSelections[mCurrentSelection];
293 int endOfSelection = currentSelection->start + currentSelection->size;
294 if(currentSelection->sizeIsEditable && frame > endOfSelection)
295 currentSelection->size = frame - currentSelection->start;
296 if(currentSelection->sizeIsEditable && currentSelection->startIsEditable && frame < endOfSelection)
298 currentSelection->start = frame;
299 currentSelection->size = endOfSelection - currentSelection->start;
301 refresh();
302 } else
303 if (modifiers & NSCommandKeyMask) {
304 beginDrag(where);
305 } else if (modifiers & NSShiftKeyMask) {
306 sendMessage(getsym("doAction"), 0, 0, 0);
307 } else if (modifiers & NSControlKeyMask) {
308 sendMessage(getsym("doMetaAction"), 0, 0, 0);
309 } else {
311 // post("current frame is %d, bounds.x is %d, where is %d \n", frame, mBounds.x, where.x);
312 if(mMoveSelection)
314 int moveframes = frame - mLastFrame;
315 mLastFrame = frame;
316 // post(" frame:%i, moveframes:%i, mLastFrame:%i \n", frame, moveframes, mLastFrame);
317 if(mSelections[mCurrentSelection].startIsEditable) mSelections[mCurrentSelection].start = mSelections[mCurrentSelection].start + moveframes;
318 } else {
320 [[NSCursor resizeRightCursor] set];
321 //[[NSCursor currentCursor] pop];
322 if(frame < mSelections[mCurrentSelection].start)
324 int endframe;
325 endframe = mSelections[mCurrentSelection].start;
326 if(mSelections[mCurrentSelection].startIsEditable) mSelections[mCurrentSelection].start = frame;
327 if(mSelections[mCurrentSelection].sizeIsEditable) mSelections[mCurrentSelection].size = endframe - mSelections[mCurrentSelection].start;
328 // post("current frame is %d, mSelectionStart is %d, where is %f \n", frame, mSelections[mCurrentSelection].start, where.x);
329 } else if(mSelections[mCurrentSelection].sizeIsEditable) mSelections[mCurrentSelection].size = frame - mSelections[mCurrentSelection].start;
331 sendMessage(getsym("doAction"), 0, 0, 0);
332 refresh();
333 // post("size: %d \n", mSelections[mCurrentSelection].size);
337 void SCSoundFileView::mouseEndTrack(SCPoint where, int modifiers, NSEvent *theEvent)
340 // mSelections[mCurrentSelection].start = sc_clip(mSelections[mCurrentSelection].start, 0, mSndBuf.frames*mResampleFactor);
341 // mSelections[mCurrentSelection].size = sc_clip(mSelections[mCurrentSelection].size, 0,
342 // (mSndBuf.frames*mResampleFactor) - mSelections[mCurrentSelection].start);
343 //mMoveSelection = false;
344 mouseTrack(where, modifiers,theEvent);
345 sendMessage(getsym("mouseEndTrack"), 0, 0, 0);
347 [[NSCursor arrowCursor] set];
351 void SCSoundFileView::draw0(SCRect inDamage, CGContextRef cgc)
353 float *data = mSndBuf.data;
354 if (!data || mIsReadingSoundFile || mScroll.x >= (mSndBuf.frames * mResampleFactor)) return;
355 int samples = mSndBuf.samples;
356 // draw scope.
357 SCRect bounds = getDrawBounds();
358 CGRect rect = SCtoCGRect(bounds);
359 int width = (int) mBounds.width - 2;
360 int channels = sc_min(mSndBuf.channels, kMaxSndChannels);
361 float chanHeight = (mBounds.height - 2.) / channels;
362 float chanHeight2 = 0.5 * chanHeight;
363 float yScale = mZoom.y * chanHeight2;
364 //iterate over channels:
365 float xscroll = mScroll.x/mResampleFactor;
366 for (int j=0; j<channels; ++j)
368 CGContextSetRGBFillColor(cgc, mWaveColors[j].red, mWaveColors[j].green, mWaveColors[j].blue, mWaveColors[j].alpha);
369 float fframe = xscroll;
370 float hzoom = mZoom.x;
371 int iframe = (int)floor(fframe);
372 int isample = iframe * channels;
373 float val = -data[isample + j];
374 int msample = 0;
376 CGRect chanRect;
377 chanRect.origin.x = rect.origin.x + 1.;
378 chanRect.origin.y = rect.origin.y + 1. + chanHeight * j + chanHeight2;
379 chanRect.size.width = width;
380 chanRect.size.height = chanHeight;
381 CGRect viewRect = CGRectMake(bounds.x + 1.f, (bounds.y + 1.f) + (j * chanHeight), bounds.width - 2.f, chanHeight);
382 //iterate over frames:
383 for (int i = 0; i < width && isample < samples; i++)
385 float ymin, ymax;
386 ymin = ymax = val;
387 float nextfframe = fframe + hzoom;
388 int nextiframe = (int)floor(nextfframe);
389 int nscan = nextiframe - iframe;
390 //nscan = nscan * channels;
391 for (int k=0; k<nscan && isample < samples; ++k)
393 val = -data[isample + j];
394 if (val < ymin) ymin = val;
395 else if (val > ymax) ymax = val;
396 isample += channels;
399 iframe = nextiframe;
400 fframe = nextfframe;
402 if(mSndMinMax.min && mSndMinMax.max && j+msample < mSndMinMax.samples){
403 mSndMinMax.min[j+msample] = ymin;
404 mSndMinMax.max[j+msample] = ymax;
405 msample += channels;
408 CGRect wrect;
409 wrect.origin.x = rect.origin.x + 1. + i;
410 wrect.size.width = 1.;
411 wrect.origin.y = chanRect.origin.y + ymin * yScale;
412 wrect.size.height = (ymax - ymin) * yScale + 1.;
414 // post("%g %g %d %g %g\n", ymin, ymax, isample, wrect.origin.x, wrect.size.height);
416 wrect = CGRectIntersection(wrect, viewRect);
417 if ( !CGRectIsNull(wrect) ) {
418 CGContextFillRect(cgc, wrect);
421 mFramesInBounds = msample;
423 if(mSndMinMax.min && mSndMinMax.max) mSndMinMax.isUsable = true;
424 else mSndMinMax.isUsable = false;
427 // this is a bit optimized version of draw0, when dragging selections the min max needn't be sorted out
428 void SCSoundFileView::draw0Buf(SCRect inDamage, CGContextRef cgc)
430 if (!mSndMinMax.isUsable) return;
431 // draw scope.
432 // post("draw0Buf. \n");
433 SCRect bounds = getDrawBounds();
434 CGRect rect = SCtoCGRect(bounds);
435 int width = (int) bounds.width - 2;
436 int channels = sc_min(mSndBuf.channels, kMaxSndChannels);
437 int widthdisp;
438 int startx = 0;
439 if(inDamage.x > 0)
440 widthdisp = (int) inDamage.width;
441 else {
442 startx = sc_abs(startx);
443 widthdisp = (int) bounds.width -2;
445 int numsamples = (int) (mFramesInBounds *mSndBuf.channels);
446 float chanHeight = (bounds.height - 2.) / channels;
447 float chanHeight2 = 0.5 * chanHeight;
448 float yScale = mZoom.y * chanHeight2;
449 //iterate over channels:
450 for (int j=0; j<channels; ++j)
452 CGContextSetRGBFillColor(cgc, mWaveColors[j].red, mWaveColors[j].green, mWaveColors[j].blue, mWaveColors[j].alpha);
453 // float fframe = mScroll.x;
454 int msample=startx*channels;
456 CGRect viewRect, chanRect;
457 viewRect = CGRectMake(bounds.x + 1.f, (bounds.y + 1.f) + (j * chanHeight), bounds.width - 2.f, chanHeight);
458 chanRect.origin.x = rect.origin.x + 1.;
459 chanRect.origin.y = rect.origin.y + 1. + chanHeight * j + chanHeight2;
460 chanRect.size.width = width;
461 chanRect.size.height = chanHeight;
462 for (int i = 0; i < widthdisp && msample<numsamples; i++)
463 //no x-zoom, offset?
465 float ymin, ymax;
466 ymin = mSndMinMax.min[j+msample];
467 ymax = mSndMinMax.max[j+msample];
468 msample += channels;
470 CGRect wrect;
471 wrect.origin.x = rect.origin.x + 1. + i;
472 wrect.size.width = 1.;
473 wrect.origin.y = chanRect.origin.y + ymin * yScale;
474 wrect.size.height = (ymax - ymin) * yScale + 1.;
476 // post("%g %g %d %g %g\n", ymin, ymax, isample, wrect.origin.x, wrect.size.height);
478 wrect = CGRectIntersection(wrect, viewRect);
479 if ( !CGRectIsNull(wrect) ) {
480 CGContextFillRect(cgc, wrect);
483 // post("msample %d\n", msample);
488 void SCSoundFileView::draw1(SCRect inDamage, CGContextRef cgc)
490 float *data = mSndBuf.data;
491 if (!data) return;
493 int samples = mSndBuf.samples;
494 SCRect bounds = getDrawBounds();
495 // draw scope.
496 CGRect rect = SCtoCGRect(bounds);
497 int width = (int) bounds.width - 2;
498 int channels = sc_min(mSndBuf.channels, kMaxSndChannels);
499 //post("channels %d\n", channels);
500 float chanHeight = bounds.height - 2.;
501 float chanHeight2 = 0.5 * chanHeight;
502 float yScale = mZoom.y * chanHeight2;
504 for (int j=0; j < channels; ++j)
506 CGContextSetRGBFillColor(cgc, mWaveColors[j].red, mWaveColors[j].green, mWaveColors[j].blue, mWaveColors[j].alpha);
507 float fframe = mScroll.x;
508 float hzoom = mZoom.x;
509 int iframe = (int)floor(fframe);
510 int isample = iframe * channels;
511 float val = -data[isample + j];
513 CGRect chanRect;
514 chanRect.origin.x = rect.origin.x + 1.;
515 chanRect.origin.y = rect.origin.y + 1. + chanHeight2;
516 chanRect.size.width = rect.size.width - 2.;
517 chanRect.size.height = chanHeight;
519 //post("width %d\n", width);
520 for (int i = 0; i < width && isample < samples; i++)
522 float ymin, ymax;
523 ymin = ymax = val;
524 float nextfframe = fframe + hzoom;
525 int nextiframe = (int)floor(nextfframe);
526 int nscan = nextiframe - iframe;
527 for (int k=0; k<nscan; ++k)
529 val = -data[isample + j];
530 if (val < ymin) ymin = val;
531 else if (val > ymax) ymax = val;
532 isample += channels;
534 iframe = nextiframe;
535 fframe = nextfframe;
537 CGRect wrect;
538 wrect.origin.x = rect.origin.x + 1. + i;
539 wrect.size.width = 1.;
540 wrect.origin.y = chanRect.origin.y + ymin * yScale;
541 wrect.size.height = (ymax - ymin) * yScale + 1.;
543 //if (i == 64) post("%g %g %g %g %g\n", ymin, ymin, wrect.origin.x, wrect.origin.y, wrect.size.height);
545 CGContextFillRect(cgc, wrect);
550 void SCSoundFileView::draw2(SCRect inDamage, CGContextRef cgc)
552 float *data = mSndBuf.data;
553 if (!data) return;
555 int samples = mSndBuf.samples;
556 SCRect bounds = getDrawBounds();
557 // draw scope.
558 CGRect rect = SCtoCGRect(bounds);
559 int channels = sc_min(mSndBuf.channels, kMaxSndChannels);
560 float height = rect.size.height - 2.;
561 float height2 = 0.5 * height;
562 float width = rect.size.width - 2.;
563 float width2 = 0.5 * width;
564 float yScale = mZoom.y * height2;
565 float xScale = mZoom.x * width2;
566 float xoff = rect.origin.x + width2 + 1.;
567 float yoff = rect.origin.y + height2 + 1.;
570 for (int k=0, j=0; j<channels; k++, j+=2)
572 CGContextSetRGBStrokeColor(cgc, mWaveColors[k].red, mWaveColors[k].green, mWaveColors[k].blue, mWaveColors[k].alpha);
573 float x = xoff + data[j] * xScale;
574 float y = yoff - data[j+1] * yScale;
575 CGContextMoveToPoint(cgc, x, y);
577 for (int i=channels; i<samples; i+=channels) {
578 x = xoff + data[i+j] * xScale;
579 y = yoff - data[i+j+1] * yScale;
580 CGContextAddLineToPoint(cgc, x, y);
582 CGContextStrokePath(cgc);
586 void SCSoundFileView::draw(SCRect inDamage)
588 CGContextRef cgc = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
589 CGContextSaveGState(cgc);
590 SCRect bounds = getDrawBounds();
591 CGRect rect = SCtoCGRect(bounds);
592 if (mBackground) mBackground->draw(cgc, rect);
593 QDDrawBevelRect(cgc, rect, 1, true);
595 float rzreciprocal = 1.f/ ( mResampleFactor * mZoom.x);
596 // mStartFrameResampled = (int)(mScroll.x / mResampleFactor);
597 CGRect viewRect, drawRect;
598 viewRect = CGRectMake(bounds.x + 1.f, bounds.y + 1.f, bounds.width - 2.f, bounds.height - 2.f);
599 for (int i=0; i<kMaxSndSelections; ++i)
601 if ( mSelections[i].size > 0 ) {
602 float selectionstart = ((mSelections[i].start-mScroll.x) * rzreciprocal ) + 1.f + bounds.x;
603 float selectionsize = mSelections[i].size * rzreciprocal;
604 drawRect = CGRectMake(selectionstart,
605 bounds.y + 1.f,
606 selectionsize,
607 bounds.height - 2.f);
608 drawRect = CGRectIntersection(drawRect, viewRect);
609 if ( !CGRectIsNull(drawRect) ) {
610 CGContextSetRGBFillColor(cgc, mSelections[i].color.red, mSelections[i].color.green, mSelections[i].color.blue, mSelections[i].color.alpha);
611 CGContextFillRect (cgc, drawRect);
616 if(!mDrawsWaveForm) goto restore;
617 // draw grid.
618 if(mGridOn && (mGridResolution > 0.0)){
619 float gridsize = ((mGridResolution * mSndBuf.samplerate) / ( mResampleFactor * mZoom.x));
620 CGContextSetRGBFillColor(cgc, mGridColor.red, mGridColor.green, mGridColor.blue, mGridColor.alpha);
621 //float xgridpos = mBounds.x + 1.;
622 float xgridpos = bounds.x + 1. - ((mScroll.x - mGridOffset) * rzreciprocal);
623 int maxwidth = (int) (bounds.width + bounds.x) - 2;
624 for (int i=0; xgridpos<maxwidth; ++i)
626 if (xgridpos > bounds.x) {
627 drawRect = CGRectMake(xgridpos,
628 bounds.y + 1.f,
629 1.f,
630 bounds.height-2.f);
631 CGContextFillRect (cgc, drawRect);
633 xgridpos += gridsize;
637 switch (mStyle) {
638 case 0 : if(mSndMinMax.isUsable && !(mTop->isScroller())) draw0Buf(inDamage, cgc); else draw0(inDamage, cgc); break;
639 case 1 : draw1(inDamage, cgc); break;
640 case 2 : draw2(inDamage, cgc); break;
644 if(mShowTimeCursor){
645 // mTimeCursorPosition is in frames
646 float xcursorpos = ((mTimeCursorPosition - mScroll.x) * rzreciprocal) + bounds.x - 1.f;
647 if ((xcursorpos > bounds.x) && (xcursorpos < bounds.x + bounds.width)) {
648 CGContextSetRGBFillColor(cgc, mTimeCursorColor.red, mTimeCursorColor.green, mTimeCursorColor.blue, mTimeCursorColor.alpha);
650 drawRect = CGRectMake(xcursorpos,
651 bounds.y + 1.f,
652 1.5f,
653 bounds.height-2.f);
654 CGContextFillRect (cgc, drawRect);
656 // post("xcursorpos %f, resampleFactor %f, mTimeCursorPosition %i \n", xcursorpos, mResampleFactor, mTimeCursorPosition);
659 restore:
660 CGContextRestoreGState(cgc);
664 int SCSoundFileView::setProperty(PyrSymbol *symbol, PyrSlot *slot)
666 int err;
668 if (symbol == s_x) {
669 double x;
670 err = slotDoubleVal(slot, &x);
671 if (err) return err;
672 mScroll.x = sc_clip(x, 0.0, mSndBuf.frames * mResampleFactor) ;
673 // mStartFrame = (int)(mScroll.x * mResampleFactor);
674 mSndMinMax.isUsable = false; //this is a straight forward solution, might be better to work this out in drawBuf
675 return errNone;
677 if (symbol == s_y) {
678 double y;
679 err = slotDoubleVal(slot, &y);
680 if (err) return err;
681 mScroll.y = y;
682 return errNone;
685 char *name = symbol->name;
686 if (strcmp(name, "bounds")==0) {
687 SCRect screct;
688 err = slotGetSCRect(slot, &screct);
689 if (err) return err;
690 if(screct.width != mBounds.width){
691 if(mElasticMode) mZoom.x = mSndBuf.frames/(screct.width-2.f);
692 mSndMinMax.isUsable = false;
693 bufAllocMinMax(&mSndMinMax, mSndBuf.channels, (int)(screct.width ));
695 setBounds(screct);
697 //pass on to SCView ...
700 if (strcmp(name,"elasticResizeMode")==0){
701 slotIntVal(slot, &mElasticMode);
702 refresh();
703 return errNone;
706 if (strcmp(name, "xZoom")==0) {
707 double x;
708 err = slotDoubleVal(slot, &x);
709 if (err) return err;
710 mZoom.x = x;
711 mInvZoom.x = 1./x;
712 mSndMinMax.isUsable = false;
713 return errNone;
715 if (strcmp(name, "yZoom")==0) {
716 double y;
717 err = slotDoubleVal(slot, &y);
718 if (err) return err;
719 mZoom.y = y;
720 mInvZoom.y = 1./y;
721 refresh();
722 return errNone;
725 if (strcmp(name, "gridResolution")==0) {
726 err = slotFloatVal(slot, &mGridResolution);
727 if (err) return err;
728 refresh();
729 return errNone;
732 if (strcmp(name, "gridOffset")==0) {
733 int gridOffset;
734 err = slotIntVal(slot, &gridOffset);
735 if (err) return err;
736 mGridOffset = gridOffset;
737 refresh();
738 return errNone;
741 if (strcmp(name, "gridColor")==0) {
742 err = slotColorVal(slot, &mGridColor);
743 if (err) return err;
744 refresh();
745 return errNone;
748 if (strcmp(name, "gridOn")==0) {
749 mGridOn = IsTrue(slot);
750 refresh();
751 return errNone;
754 if (strcmp(name, "selectionColor")==0) {
755 if (!isKindOfSlot(slot, class_array)) return errWrongType;
756 PyrSlot *slots = slotRawObject(slot)->slots;
757 int index;
758 err = slotIntVal(slots+0, &index);
759 if (err) return err;
760 err = slotColorVal(slots+1, &(mSelections+index)->color);
761 if (err) return err;
762 refresh();
763 return errNone;
766 if (strcmp(name, "waveColors")==0) {
767 if (!isKindOfSlot(slot, class_array)) return errWrongType;
768 PyrSlot *slots = slotRawObject(slot)->slots;
769 for (int i=0; i<slotRawObject(slot)->size; ++i)
771 err = slotColorVal(slots+i, mWaveColors+i);
772 if (err) return err;
774 refresh();
775 return errNone;
777 if (strcmp(name, "style")==0) {
778 err = slotIntVal(slot, &mStyle);
779 if (err) return err;
780 refresh();
781 return errNone;
783 if (strcmp(name, "drawsWaveForm")==0) {
784 mDrawsWaveForm = IsTrue(slot);
785 refresh();
786 return errNone;
788 if(strcmp(name, "readSndFile")==0){
789 int resample, startframe, frames, numframes;
790 //float samplerate;
791 SF_INFO info;
792 if (!isKindOfSlot(slot, class_array)) return errWrongType;
793 PyrSlot *slots = slotRawObject(slot)->slots;
794 SNDFILE * file = (SNDFILE*) slotRawPtr(slotRawObject(slots+0)->slots);
795 err = slotIntVal(slots+1, &startframe);
796 if (err) return err;
797 err = slotIntVal(slots+2, &frames);
798 if (err) return err;
799 err = slotIntVal(slots+3, &resample);
800 if (err) return err;
801 err = slotIntVal(slots+4, &info.samplerate);
802 if (err) return err;
803 err = slotIntVal(slots+5, &numframes);
804 if (err) return err;
805 info.frames = (sf_count_t) numframes;
806 err = slotIntVal(slots+6, &info.channels);
807 if (err) return err;
808 mSndMinMax.isUsable = false;
809 err = readSndFile(file, startframe, frames, resample, info);
810 if (err) return err;
811 //refresh();
812 return errNone;
814 if (strcmp(name, "currentSelection")==0) {
815 err = slotIntVal(slot, &mCurrentSelection);
816 if (err) return err;
817 return errNone;
819 if (strcmp(name, "selectionStart")==0) {
820 if (!isKindOfSlot(slot, class_array)) return errWrongType;
821 PyrSlot *slots = slotRawObject(slot)->slots;
822 int index;
823 err = slotIntVal(slots+0, &index);
824 if (err) return err;
825 err = slotIntVal(slots+1, &(mSelections+index)->start);
826 if (err) return err;
827 refresh();
828 return errNone;
831 if (strcmp(name, "selectionSize")==0) {
832 if (!isKindOfSlot(slot, class_array)) return errWrongType;
833 PyrSlot *slots = slotRawObject(slot)->slots;
834 int index;
835 err = slotIntVal(slots+0, &index);
836 if (err) return err;
837 err = slotIntVal(slots+1, &(mSelections+index)->size);
838 if (err) return err;
839 refresh();
840 return errNone;
843 if (strcmp(name, "selectionStartIsEditable")==0) {
844 if (!isKindOfSlot(slot, class_array)) return errWrongType;
845 PyrSlot *slots = slotRawObject(slot)->slots;
846 int index;
847 err = slotIntVal(slots+0, &index);
848 if (err) return err;
849 (mSelections+index)->startIsEditable = IsTrue(slots+1);
850 return errNone;
853 if (strcmp(name, "selectionSizeIsEditable")==0) {
854 if (!isKindOfSlot(slot, class_array)) return errWrongType;
855 PyrSlot *slots = slotRawObject(slot)->slots;
856 int index;
857 err = slotIntVal(slots+0, &index);
858 if (err) return err;
859 (mSelections+index)->sizeIsEditable = IsTrue(slots+1);
860 return errNone;
863 if (strcmp(name, "setViewData")==0) {
864 int startFrame;
865 if (!isKindOfSlot(slot, class_array)) return errWrongType;
866 PyrSlot *slots = slotRawObject(slot)->slots;
867 err = slotFloatVal(slots+1, &mResampleFactor);
868 if(err) return err;
869 err = slotIntVal(slots+2, &startFrame);
870 if(err) return err;
871 int channels, samplerate;
872 err = slotIntVal(slots+3, &channels);
873 if(err) return err;
874 err = slotIntVal(slots+4, &samplerate);
875 if(err) return err;
876 if (!isKindOfSlot(slots+0, class_array)) return errWrongType;
877 int size = slotRawObject(slots+0)->size;
878 mScroll.x = (float) startFrame;
879 if(size == mSndBuf.samples && mSndBuf.data && mSndBuf.channels==channels && mSndBuf.samplerate == samplerate){
880 PyrDoubleArray * arr;
881 arr = (PyrDoubleArray*) slotRawObject(slots+0);
882 for(int i=0; i<size; i++){
883 mSndBuf.data[i] = arr->d[i];
885 } else {
886 int frames = (int) ceil(size/channels);
887 int err = bufAlloc(&mSndBuf, channels, frames, samplerate);
888 if(err) return err;
889 PyrDoubleArray * arr;
890 // printf("frames:%i, size:%i, mSndBuf.samples:%i\n",frames, size, mSndBuf.samples);
891 arr = (PyrDoubleArray*) slotRawObject(slots+0);
892 size = mSndBuf.samples;
893 for(int i=0; i<size; i++){
894 mSndBuf.data[i] = arr->d[i];
897 if(mElasticMode){
898 // mZoom.x = ((mSndBuf.frames/mResampleFactor)/(mBounds.width-2.f));
899 mZoom.x = mSndBuf.frames/(mBounds.width-2.f);
902 mSndMinMax.isUsable = false;
903 err = bufAllocMinMax(&mSndMinMax, mSndBuf.channels, (int)(mBounds.width ));
904 refresh();
905 return errNone;
907 if (strcmp(name, "showTimeCursor")==0) {
908 mShowTimeCursor = IsTrue(slot);
909 refresh();
910 return errNone;
912 if (strcmp(name, "timeCursorPosition")==0) {
913 err = slotIntVal(slot, &mTimeCursorPosition);
914 if(err) return err;
915 refresh();
916 return errNone;
918 if (strcmp(name, "timeCursorColor")==0) {
919 err = slotColorVal(slot, &mTimeCursorColor);
920 if (err) return err;
921 refresh();
922 return errNone;
924 return SCView::setProperty(symbol, slot);
927 int SCSoundFileView::getProperty(PyrSymbol *symbol, PyrSlot *slot)
929 if (symbol == s_x) {
930 SetFloat(slot, mScroll.x);
931 return errNone;
933 if (symbol == s_y) {
934 SetFloat(slot, mScroll.y);
935 return errNone;
938 char *name = symbol->name;
939 if (strcmp(name, "xZoom")==0) {
940 SetFloat(slot, mZoom.x);
941 return errNone;
943 if (strcmp(name, "yZoom")==0) {
944 SetFloat(slot, mZoom.y);
945 return errNone;
947 // if (strcmp(name, "bufnum")==0) {
948 // SetInt(slot, mBufNum);
949 // return errNone;
950 // }
951 if (strcmp(name, "gridColor")==0) {
952 return setSlotColor(slot, &mGridColor);
954 if (strcmp(name, "waveColors")==0) {
955 if (!isKindOfSlot(slot, class_array)) return errWrongType;
956 PyrSlot *slots = slotRawObject(slot)->slots;
957 for (int i=0; i<slotRawObject(slot)->size; ++i)
959 int err = setSlotColor(slots+i, mWaveColors+i);
960 if (err) return err;
962 return errNone;
964 // if (strcmp(name, "selections")==0)
965 // {
966 // if (!isKindOfSlot(slot, class_array)) return errWrongType;
967 // int size = (slotRawObject(slot)->slots[0]).uo->size;
968 // size = sc_clip(size, 0, kMaxSndSelections);
969 // PyrDoubleArray * startarr;
970 // PyrDoubleArray * rangearr;
971 // startarr = ((PyrDoubleArray*)(slotRawObject(slot)->slots[0].uo));
972 // rangearr = ((PyrDoubleArray*)(slotRawObject(slot)->slots[1].uo));
974 // for(int i=0; i<size; i++){
975 // startarr->d[i] = mSelections[i].start;
976 // rangearr->d[i] = mSelections[i].size;
977 // }
978 // return errNone;
979 // }
981 if (strcmp(name, "selections")==0)
983 if (!isKindOfSlot(slot, class_array)) return errWrongType;
984 int size = slotRawObject(slot)->size;
985 size = sc_clip(size, 0, kMaxSndSelections);
986 PyrObject *array = slotRawObject(slot);
988 for(int i=0; i<size; i++){
989 PyrSlot *slots= slotRawObject(array->slots+i)->slots;
990 SetInt(slots+0, mSelections[i].start);
991 SetInt(slots+1, mSelections[i].size);
993 return errNone;
996 if (strcmp(name, "absoluteX")==0) {
997 SetInt(slot, (int) mAbsolutePosition.x);
998 return errNone;
1000 if (strcmp(name, "absoluteY")==0) {
1001 SetInt(slot, (int) mAbsolutePosition.y);
1002 return errNone;
1004 if (strcmp(name, "currentSelection")==0) {
1005 SetInt(slot, (int) mCurrentSelection);
1006 return errNone;
1008 if (strcmp(name, "selectionStart")==0) {
1009 int index;
1010 int err = slotIntVal(slot, &index);
1011 if (err) return err;
1012 SetInt(slot, (int) mSelections[index].start);
1013 return errNone;
1016 if (strcmp(name, "selectionSize")==0) {
1017 int index;
1018 int err = slotIntVal(slot, &index);
1019 if (err) return err;
1020 SetInt(slot, (int) mSelections[index].size);
1021 return errNone;
1024 if (strcmp(name, "selectionStartTime")==0) {
1025 int index;
1026 int err = slotIntVal(slot, &index);
1027 if (err) return err;
1028 SetFloat(slot, (float) mSelections[index].start/mSndBuf.samplerate);
1029 return errNone;
1032 if (strcmp(name, "selectionDuration")==0) {
1033 int index;
1034 int err = slotIntVal(slot, &index);
1035 if (err) return err;
1036 SetFloat(slot, (float) mSelections[index].size/mSndBuf.samplerate);
1037 return errNone;
1040 if (strcmp(name, "getViewData")==0) {
1042 if (!isKindOfSlot(slot, class_array)) return errWrongType;
1043 int size = slotRawObject(slot)->size;
1044 size = sc_clip(size, 0, mSndBuf.samples);
1045 PyrDoubleArray * arr;
1046 arr = (PyrDoubleArray*) slotRawObject(slot);
1047 for(int i=0; i<size; i++){
1048 arr->d[i] = mSndBuf.data[i];
1050 return errNone;
1052 if (strcmp(name, "getViewNumSamples")==0) {
1054 SetInt(slot, (int) mSndBuf.samples);
1055 return errNone;
1057 if (strcmp(name, "timeCursorPosition")==0) {
1059 SetInt(slot, mTimeCursorPosition);
1060 return errNone;
1062 if (strcmp(name, "gridOn")==0) {
1064 SetBool(slot, mGridOn);
1065 return errNone;
1067 if (strcmp(name, "gridResolution")==0) {
1069 SetFloat(slot, mGridResolution);
1070 return errNone;
1072 if (strcmp(name, "timeCursorOn")==0) {
1074 SetBool(slot, mShowTimeCursor);
1075 return errNone;
1077 if (strcmp(name, "timeCursorColor")==0) {
1079 return setSlotColor(slot, &mTimeCursorColor);
1081 return SCView::getProperty(symbol, slot);