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>
32 #include "PyrSymbol.h"
33 #include "PyrPrimitive.h"
34 #include "PyrObject.h"
35 #include "PyrKernel.h"
37 #include "VMGlobals.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
)
62 void* ptr
= malloc(size
);
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
;
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
;
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;
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);
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()
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;
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
);
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
));
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
];
197 for(int i
=0; i
<frames
; i
+=readsize
){
198 sf_read_float(file
, data
, readsize
*channels
);
200 for (int j
=0; j
<channels
; ++j
)
202 float ymin
, ymax
, val
;
205 //read through all samples
206 for(int k
=0; k
<(readsize
*channels
); k
+=channels
)
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
;
219 sf_read_float(file
, mSndBuf.data
, frames
*channels
);
221 mIsReadingSoundFile
= false;
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
)
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;
241 bufAllocMinMax(&mSndMinMax
, mSndBuf.channels
, (int)(inBounds.width
));
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
;
251 mLayout.bounds
= mBounds
;
255 void SCSoundFileView
::mouseBeginTrack(SCPoint where
, int modifiers
, NSEvent
*theEvent
)
257 //mPrevPoint = where;
258 //mouseTrack(where, modifiers,theEvent);
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
) {
267 //check if there is a selection:
268 int selection
= findSelection(frame
);
270 mMoveSelection
= true;
271 mCurrentSelection
= selection
;
272 [[NSCursor openHandCursor
] set
];
275 mMoveSelection
= false;
276 if(mSelections
[mCurrentSelection
].startIsEditable
) mSelections
[mCurrentSelection
].start
= frame
;
279 mTimeCursorPosition
= 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
;
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
;
303 if (modifiers
& NSCommandKeyMask
) {
305 } else if (modifiers
& NSShiftKeyMask
) {
306 sendMessage(getsym("doAction"), 0, 0, 0);
307 } else if (modifiers
& NSControlKeyMask
) {
308 sendMessage(getsym("doMetaAction"), 0, 0, 0);
311 // post("current frame is %d, bounds.x is %d, where is %d \n", frame, mBounds.x, where.x);
314 int moveframes
= frame
- mLastFrame
;
316 // post(" frame:%i, moveframes:%i, mLastFrame:%i \n", frame, moveframes, mLastFrame);
317 if(mSelections
[mCurrentSelection
].startIsEditable
) mSelections
[mCurrentSelection
].start
= mSelections
[mCurrentSelection
].start
+ moveframes
;
320 [[NSCursor resizeRightCursor
] set
];
321 //[[NSCursor currentCursor] pop];
322 if(frame
< mSelections
[mCurrentSelection
].start
)
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);
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
;
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
];
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
++)
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
;
402 if(mSndMinMax.min
&& mSndMinMax.max
&& j
+msample
< mSndMinMax.samples
){
403 mSndMinMax.min
[j
+msample
] = ymin
;
404 mSndMinMax.max
[j
+msample
] = ymax
;
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;
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
);
440 widthdisp
= (int) inDamage.width
;
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
++)
466 ymin
= mSndMinMax.min
[j
+msample
];
467 ymax
= mSndMinMax.max
[j
+msample
];
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
;
493 int samples
= mSndBuf.samples
;
494 SCRect bounds
= getDrawBounds();
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
];
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
++)
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
;
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
;
555 int samples
= mSndBuf.samples
;
556 SCRect bounds
= getDrawBounds();
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
,
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
;
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
,
631 CGContextFillRect (cgc
, drawRect
);
633 xgridpos
+= gridsize
;
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;
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
,
654 CGContextFillRect (cgc
, drawRect
);
656 // post("xcursorpos %f, resampleFactor %f, mTimeCursorPosition %i \n", xcursorpos, mResampleFactor, mTimeCursorPosition);
660 CGContextRestoreGState(cgc
);
664 int SCSoundFileView
::setProperty(PyrSymbol
*symbol
, PyrSlot
*slot
)
670 err
= slotDoubleVal(slot
, &x
);
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
679 err
= slotDoubleVal(slot
, &y
);
685 char *name
= symbol
->name
;
686 if (strcmp(name
, "bounds")==0) {
688 err
= slotGetSCRect(slot
, &screct
);
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
));
697 //pass on to SCView ...
700 if (strcmp(name
,"elasticResizeMode")==0){
701 slotIntVal(slot
, &mElasticMode
);
706 if (strcmp(name
, "xZoom")==0) {
708 err
= slotDoubleVal(slot
, &x
);
712 mSndMinMax.isUsable
= false;
715 if (strcmp(name
, "yZoom")==0) {
717 err
= slotDoubleVal(slot
, &y
);
725 if (strcmp(name
, "gridResolution")==0) {
726 err
= slotFloatVal(slot
, &mGridResolution
);
732 if (strcmp(name
, "gridOffset")==0) {
734 err
= slotIntVal(slot
, &gridOffset
);
736 mGridOffset
= gridOffset
;
741 if (strcmp(name
, "gridColor")==0) {
742 err
= slotColorVal(slot
, &mGridColor
);
748 if (strcmp(name
, "gridOn")==0) {
749 mGridOn
= IsTrue(slot
);
754 if (strcmp(name
, "selectionColor")==0) {
755 if (!isKindOfSlot(slot
, class_array
)) return errWrongType
;
756 PyrSlot
*slots
= slotRawObject(slot
)->slots
;
758 err
= slotIntVal(slots
+0, &index
);
760 err
= slotColorVal(slots
+1, &(mSelections
+index
)->color
);
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
);
777 if (strcmp(name
, "style")==0) {
778 err
= slotIntVal(slot
, &mStyle
);
783 if (strcmp(name
, "drawsWaveForm")==0) {
784 mDrawsWaveForm
= IsTrue(slot
);
788 if(strcmp(name
, "readSndFile")==0){
789 int resample
, startframe
, frames
, numframes
;
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
);
797 err
= slotIntVal(slots
+2, &frames
);
799 err
= slotIntVal(slots
+3, &resample
);
801 err
= slotIntVal(slots
+4, &info.samplerate
);
803 err
= slotIntVal(slots
+5, &numframes
);
805 info.frames
= (sf_count_t
) numframes
;
806 err
= slotIntVal(slots
+6, &info.channels
);
808 mSndMinMax.isUsable
= false;
809 err
= readSndFile(file
, startframe
, frames
, resample
, info
);
814 if (strcmp(name
, "currentSelection")==0) {
815 err
= slotIntVal(slot
, &mCurrentSelection
);
819 if (strcmp(name
, "selectionStart")==0) {
820 if (!isKindOfSlot(slot
, class_array
)) return errWrongType
;
821 PyrSlot
*slots
= slotRawObject(slot
)->slots
;
823 err
= slotIntVal(slots
+0, &index
);
825 err
= slotIntVal(slots
+1, &(mSelections
+index
)->start
);
831 if (strcmp(name
, "selectionSize")==0) {
832 if (!isKindOfSlot(slot
, class_array
)) return errWrongType
;
833 PyrSlot
*slots
= slotRawObject(slot
)->slots
;
835 err
= slotIntVal(slots
+0, &index
);
837 err
= slotIntVal(slots
+1, &(mSelections
+index
)->size
);
843 if (strcmp(name
, "selectionStartIsEditable")==0) {
844 if (!isKindOfSlot(slot
, class_array
)) return errWrongType
;
845 PyrSlot
*slots
= slotRawObject(slot
)->slots
;
847 err
= slotIntVal(slots
+0, &index
);
849 (mSelections
+index
)->startIsEditable
= IsTrue(slots
+1);
853 if (strcmp(name
, "selectionSizeIsEditable")==0) {
854 if (!isKindOfSlot(slot
, class_array
)) return errWrongType
;
855 PyrSlot
*slots
= slotRawObject(slot
)->slots
;
857 err
= slotIntVal(slots
+0, &index
);
859 (mSelections
+index
)->sizeIsEditable
= IsTrue(slots
+1);
863 if (strcmp(name
, "setViewData")==0) {
865 if (!isKindOfSlot(slot
, class_array
)) return errWrongType
;
866 PyrSlot
*slots
= slotRawObject(slot
)->slots
;
867 err
= slotFloatVal(slots
+1, &mResampleFactor
);
869 err
= slotIntVal(slots
+2, &startFrame
);
871 int channels
, samplerate
;
872 err
= slotIntVal(slots
+3, &channels
);
874 err
= slotIntVal(slots
+4, &samplerate
);
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
];
886 int frames
= (int) ceil(size
/channels
);
887 int err
= bufAlloc(&mSndBuf
, channels
, frames
, samplerate
);
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
];
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
));
907 if (strcmp(name
, "showTimeCursor")==0) {
908 mShowTimeCursor
= IsTrue(slot
);
912 if (strcmp(name
, "timeCursorPosition")==0) {
913 err
= slotIntVal(slot
, &mTimeCursorPosition
);
918 if (strcmp(name
, "timeCursorColor")==0) {
919 err
= slotColorVal(slot
, &mTimeCursorColor
);
924 return SCView
::setProperty(symbol
, slot
);
927 int SCSoundFileView
::getProperty(PyrSymbol
*symbol
, PyrSlot
*slot
)
930 SetFloat(slot
, mScroll.x
);
934 SetFloat(slot
, mScroll.y
);
938 char *name
= symbol
->name
;
939 if (strcmp(name
, "xZoom")==0) {
940 SetFloat(slot
, mZoom.x
);
943 if (strcmp(name
, "yZoom")==0) {
944 SetFloat(slot
, mZoom.y
);
947 // if (strcmp(name, "bufnum")==0) {
948 // SetInt(slot, mBufNum);
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
);
964 // if (strcmp(name, "selections")==0)
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;
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
);
996 if (strcmp(name
, "absoluteX")==0) {
997 SetInt(slot
, (int) mAbsolutePosition.x
);
1000 if (strcmp(name
, "absoluteY")==0) {
1001 SetInt(slot
, (int) mAbsolutePosition.y
);
1004 if (strcmp(name
, "currentSelection")==0) {
1005 SetInt(slot
, (int) mCurrentSelection
);
1008 if (strcmp(name
, "selectionStart")==0) {
1010 int err
= slotIntVal(slot
, &index
);
1011 if (err
) return err
;
1012 SetInt(slot
, (int) mSelections
[index
].start
);
1016 if (strcmp(name
, "selectionSize")==0) {
1018 int err
= slotIntVal(slot
, &index
);
1019 if (err
) return err
;
1020 SetInt(slot
, (int) mSelections
[index
].size
);
1024 if (strcmp(name
, "selectionStartTime")==0) {
1026 int err
= slotIntVal(slot
, &index
);
1027 if (err
) return err
;
1028 SetFloat(slot
, (float) mSelections
[index
].start
/mSndBuf.samplerate
);
1032 if (strcmp(name
, "selectionDuration")==0) {
1034 int err
= slotIntVal(slot
, &index
);
1035 if (err
) return err
;
1036 SetFloat(slot
, (float) mSelections
[index
].size
/mSndBuf.samplerate
);
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
];
1052 if (strcmp(name
, "getViewNumSamples")==0) {
1054 SetInt(slot
, (int) mSndBuf.samples
);
1057 if (strcmp(name
, "timeCursorPosition")==0) {
1059 SetInt(slot
, mTimeCursorPosition
);
1062 if (strcmp(name
, "gridOn")==0) {
1064 SetBool(slot
, mGridOn
);
1067 if (strcmp(name
, "gridResolution")==0) {
1069 SetFloat(slot
, mGridResolution
);
1072 if (strcmp(name
, "timeCursorOn")==0) {
1074 SetBool(slot
, mShowTimeCursor
);
1077 if (strcmp(name
, "timeCursorColor")==0) {
1079 return setSlotColor(slot
, &mTimeCursorColor
);
1081 return SCView
::getProperty(symbol
, slot
);