FFT: Prevent user from attempting hops smaller than SC's block size
[supercollider.git] / server / plugins / DiskIO_UGens.cpp
blob4e35368d6b6da80f8d1d621f27a48363d2fc4816
1 /*
2 SuperCollider real time audio synthesis system
3 Copyright (c) 2002 James McCartney. All rights reserved.
4 http://www.audiosynth.com
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 Output of frame index for DiskOut by Marije Baalman (nescivi), 2009
22 VDiskIn by Scott Wilson, 2008
25 #include "MsgFifo.h"
26 #include "SC_SyncCondition.h"
27 #include "SC_PlugIn.h"
28 #ifdef _WIN32
29 #include <sndfile-win.h>
30 #else
31 #include <sndfile.h>
32 #endif
34 #include <new>
36 static InterfaceTable *ft;
38 const int kMAXDISKCHANNELS = 32;
40 enum {
41 diskinIdle,
42 diskinStartingEmpty,
43 diskinStartingFull,
44 diskinNormal,
45 diskinLastBuffer,
46 diskinEndSilence
49 enum {
50 diskoutIdle,
51 diskoutNormal
55 struct DiskIn : public Unit
57 float m_fbufnum;
58 SndBuf *m_buf;
59 uint32 m_framepos;
62 struct DiskOut : public Unit
64 float m_fbufnum;
65 SndBuf *m_buf;
66 uint32 m_framepos;
67 uint32 m_framewritten;
70 struct VDiskIn : public Unit
72 float m_fbufnum, m_pchRatio, m_rBufSize;
73 double m_framePos, m_bufPos;
74 uint32 m_count;
75 SndBuf *m_buf;
78 //////////////////////////////////////////////////////////////////////////////////////////////////
80 extern "C"
83 void DiskIn_next(DiskIn *unit, int inNumSamples);
84 void DiskIn_Ctor(DiskIn* unit);
86 void DiskOut_next(DiskOut *unit, int inNumSamples);
87 void DiskOut_Ctor(DiskOut* unit);
89 void VDiskIn_next(VDiskIn *unit, int inNumSamples);
90 void VDiskIn_first(VDiskIn *unit, int inNumSamples);
91 void VDiskIn_Ctor(VDiskIn* unit);
94 //////////////////////////////////////////////////////////////////////////////////////////////////
97 enum {
98 kDiskCmd_Read,
99 kDiskCmd_Write,
100 kDiskCmd_ReadLoop,
103 namespace
106 struct DiskIOMsg
108 World *mWorld;
109 int16 mCommand;
110 int16 mChannels;
111 int32 mBufNum;
112 int32 mPos;
113 int32 mFrames;
115 void Perform();
118 void DiskIOMsg::Perform()
120 NRTLock(mWorld);
122 SndBuf *buf = World_GetNRTBuf(mWorld, mBufNum);
123 if (mPos > buf->frames || mPos + mFrames > buf->frames || buf->channels != mChannels) goto leave;
125 sf_count_t count;
126 switch (mCommand) {
127 case kDiskCmd_Read :
128 count = buf->sndfile ? sf_readf_float(buf->sndfile, buf->data + mPos * buf->channels, mFrames) : 0;
129 if (count < mFrames) {
130 memset(buf->data + (mPos + count) * buf->channels, 0, (mFrames - count) * buf->channels * sizeof(float));
131 World_GetBuf(mWorld, mBufNum)->mask = mPos + count;
132 // NOTE: Possible race condition above: The disk IO thread may write to the rt SndBuf
133 // while the stage3 of the sequenced commands copies the non-rt SndBuf struct to the rt buf.
134 // This only happens if the buffer is modified via an osc command.
135 // We can't use the non-rt SndBuf above since buf->mask won't be reflected to the rt buf.
137 break;
138 case kDiskCmd_ReadLoop :
139 if (!buf->sndfile) {
140 memset(buf->data + mPos * buf->channels, 0, mFrames * buf->channels * sizeof(float));
141 goto leave;
143 count = sf_readf_float(buf->sndfile, buf->data + mPos * buf->channels, mFrames);
144 while (mFrames -= count) {
145 sf_seek(buf->sndfile, 0, SEEK_SET);
146 count = sf_readf_float(buf->sndfile, buf->data + (mPos + count) * buf->channels, mFrames);
148 break;
149 case kDiskCmd_Write :
150 //printf("kDiskCmd_Write %d %p\n", mBufNum, buf->sndfile);
151 if (!buf->sndfile) goto leave;
152 count = sf_writef_float(buf->sndfile, buf->data + mPos * buf->channels, mFrames);
153 break;
156 leave:
157 NRTUnlock(mWorld);
160 MsgFifoNoFree<DiskIOMsg, 256> gDiskFifo;
161 SC_SyncCondition gDiskFifoHasData;
163 void* disk_io_thread_func(void* arg)
165 while (true) {
166 gDiskFifoHasData.WaitEach();
167 gDiskFifo.Perform();
169 return 0;
174 //////////////////////////////////////////////////////////////////////////////////////////////////
176 #define MAXCHANNELS 32
178 #define SETUP_OUT(offset) \
179 if (unit->mNumOutputs != bufChannels) { \
180 ClearUnitOutputs(unit, inNumSamples); \
181 return; \
183 float *out[MAXCHANNELS]; \
184 for (uint32 i=0; i<bufChannels; ++i) out[i] = OUT(i+offset);
186 #define SETUP_IN(offset) \
187 if (unit->mNumInputs - (uint32)offset != bufChannels) { \
188 ClearUnitOutputs(unit, inNumSamples); \
189 return; \
191 float *in[MAXCHANNELS]; \
192 for (uint32 i=0; i<bufChannels; ++i) in[i] = IN(i+offset);
194 void DiskIn_Ctor(DiskIn* unit)
196 unit->m_fbufnum = -1.f;
197 unit->m_buf = unit->mWorld->mSndBufs;
198 unit->m_framepos = 0;
199 SETCALC(DiskIn_next);
202 void DiskIn_next(DiskIn *unit, int inNumSamples)
204 GET_BUF_SHARED
205 if (!bufData || ((bufFrames & ((unit->mWorld->mBufLength<<1) - 1)) != 0)) {
206 unit->m_framepos = 0;
207 ClearUnitOutputs(unit, inNumSamples);
208 return;
210 SETUP_OUT(0)
212 if (unit->m_framepos >= bufFrames) {
213 unit->m_framepos = 0;
216 bufData += unit->m_framepos * bufChannels;
218 // buffer must be allocated as a multiple of 2*blocksize.
219 if (bufChannels > 2) {
220 for (int j=0; j<inNumSamples; ++j) {
221 for (uint32 i=0; i<bufChannels; ++i) {
222 *out[i]++ = *bufData++;
225 } else if (bufChannels == 2) {
226 float *out0 = out[0];
227 float *out1 = out[1];
228 for (int j=0; j<inNumSamples; ++j) {
229 *out0++ = *bufData++;
230 *out1++ = *bufData++;
232 } else {
233 float *out0 = out[0];
234 for (int j=0; j<inNumSamples; ++j) {
235 *out0++ = *bufData++;
238 if(unit->m_buf->mask1>=0 && unit->m_framepos>=unit->m_buf->mask1) unit->mDone = true;
239 unit->m_framepos += inNumSamples;
240 uint32 bufFrames2 = bufFrames >> 1;
241 if (unit->m_framepos == bufFrames) {
242 unit->m_framepos = 0;
243 goto sendMessage;
244 } else if (unit->m_framepos == bufFrames2) {
245 sendMessage:
246 if (unit->m_buf->mask>=0) unit->m_buf->mask1 = unit->m_buf->mask;
247 if(unit->mWorld->mRealTime){
248 // send a message to read
249 DiskIOMsg msg;
250 msg.mWorld = unit->mWorld;
251 msg.mCommand = (int)ZIN0(1) ? kDiskCmd_ReadLoop : kDiskCmd_Read;
252 msg.mBufNum = (int)fbufnum;
253 msg.mPos = bufFrames2 - unit->m_framepos;
254 msg.mFrames = bufFrames2;
255 msg.mChannels = bufChannels;
256 gDiskFifo.Write(msg);
257 gDiskFifoHasData.Signal();
258 } else {
259 SndBuf *bufr = World_GetNRTBuf(unit->mWorld, (int) fbufnum);
260 uint32 mPos = bufFrames2 - unit->m_framepos;
261 if (mPos > (uint32)bufr->frames || mPos + bufFrames2 > (uint32)bufr->frames || (uint32) bufr->channels != bufChannels) return;
262 sf_count_t count;
264 if ((int)ZIN0(1)) { // loop
265 if (!bufr->sndfile) memset(bufr->data + mPos * bufr->channels, 0, bufFrames2 * bufr->channels * sizeof(float));
266 count = sf_readf_float(bufr->sndfile, bufr->data + mPos * bufr->channels, bufFrames2);
267 while (bufFrames2 -= count) {
268 sf_seek(bufr->sndfile, 0, SEEK_SET);
269 count = sf_readf_float(bufr->sndfile, bufr->data + (mPos + count) * bufr->channels, bufFrames2);
271 } else { // non-loop
272 count = bufr->sndfile ? sf_readf_float(bufr->sndfile, bufr->data + mPos * bufr->channels, bufFrames2) : 0;
273 if (count < bufFrames2) {
274 memset(bufr->data + (mPos + count) * bufr->channels, 0, (bufFrames2 - count) * bufr->channels * sizeof(float));
275 unit->m_buf->mask = mPos + count;
282 ////////////////////////////////////////////////////////////////////////////////////////////////////////
284 void DiskOut_Ctor(DiskOut* unit)
286 unit->m_fbufnum = -1.f;
287 unit->m_buf = unit->mWorld->mSndBufs;
288 unit->m_framepos = 0;
289 unit->m_framewritten = 0;
290 SETCALC(DiskOut_next);
294 void DiskOut_next(DiskOut *unit, int inNumSamples)
296 GET_BUF
298 if (!bufData || ((bufFrames & ((unit->mWorld->mBufLength<<1) - 1)) != 0)) {
299 unit->m_framepos = 0;
300 // unit->m_framewritten = 0;
301 return;
303 SETUP_IN(1)
305 float *out = OUT(0);
306 uint32 framew = unit->m_framewritten;
308 // printf("Start frames %i %i\n", unit->m_framewritten, framew );
310 if (unit->m_framepos >= bufFrames) {
311 unit->m_framepos = 0;
314 bufData += unit->m_framepos * bufChannels;
316 if (bufChannels > 2) {
317 for (int j=0; j<inNumSamples; ++j) {
318 for (uint32 i=0; i<bufChannels; ++i) {
319 *bufData++ = *in[i]++;
321 out[j] = framew++;
323 } else if (bufChannels == 2) {
324 float *in0 = in[0];
325 float *in1 = in[1];
326 for (int j=0; j<inNumSamples; ++j) {
327 *bufData++ = *in0++;
328 *bufData++ = *in1++;
329 out[j] = framew++;
331 } else {
332 float *in0 = in[0];
333 for (int j=0; j<inNumSamples; ++j) {
334 *bufData++ = *in0++;
335 out[j] = framew++;
339 unit->m_framepos += inNumSamples;
340 // unit->m_framewritten += inNumSamples;
341 unit->m_framewritten = framew;
343 // printf("frames %i %i\n", unit->m_framewritten, framew );
345 uint32 bufFrames2 = bufFrames >> 1;
346 if (unit->m_framepos == bufFrames) {
347 unit->m_framepos = 0;
348 goto sendMessage;
349 } else if (unit->m_framepos == bufFrames2) {
350 sendMessage:
351 // send a message to write
352 DiskIOMsg msg;
353 msg.mWorld = unit->mWorld;
354 msg.mCommand = kDiskCmd_Write;
355 msg.mBufNum = (int)fbufnum;
356 msg.mPos = bufFrames2 - unit->m_framepos;
357 msg.mFrames = bufFrames2;
358 msg.mChannels = bufChannels;
359 //printf("sendMessage %d %d %d %d\n", msg.mBufNum, msg.mPos, msg.mFrames, msg.mChannels);
360 gDiskFifo.Write(msg);
361 gDiskFifoHasData.Signal();
367 void VDiskIn_Ctor(VDiskIn* unit)
369 unit->m_fbufnum = -1.f;
370 unit->m_buf = unit->mWorld->mSndBufs;
371 unit->m_framePos = 0.;
372 unit->m_bufPos = 0.;
373 unit->m_pchRatio = sc_max(IN0(1), 0.f);
374 unit->m_count = 0;
376 SETCALC(VDiskIn_first); // should be first
379 // first time through, the FIRST sample doesn't need the interpolation... the buffer won't be filled 'correctly' for
380 // interpolation, so use the _first function to make this exception
382 void VDiskIn_first(VDiskIn *unit, int inNumSamples)
385 SETCALC(VDiskIn_next);
387 float a, b, c, d, oldBufPos;
388 bool test = false;
390 GET_BUF_SHARED
392 if (!bufData || ((bufFrames & ((unit->mWorld->mBufLength<<1) - 1)) != 0)) {
393 unit->m_framePos = 0.;
394 unit->m_count = 0;
395 ClearUnitOutputs(unit, inNumSamples);
396 return;
399 uint32 bufFrames2 = bufFrames >> 1;
400 float fbufFrames2 = (float)bufFrames2;
401 float fbufFrames = (float)bufFrames;
402 unit->m_rBufSize = 1. / bufFrames;
404 SETUP_OUT(0)
406 float framePos = unit->m_framePos;
407 float bufPos = unit->m_bufPos; // where we are in the DiskIn buffer
408 float newPchRatio = sc_max(IN0(1), 0.f);
410 if ((newPchRatio * inNumSamples * unit->m_rBufSize) >= 0.5) {
411 printf("pitch ratio is greater then max allowed (see VDiskIn help)\n");
412 ClearUnitOutputs(unit, inNumSamples);
413 SETCALC(VDiskIn_first);
414 return;
417 float pchRatio = unit->m_pchRatio;
418 float pchSlope = CALCSLOPE(newPchRatio, pchRatio);
420 const float* tableInit = bufData;
422 for (uint32 i = 0; i < bufChannels; i++){
423 out[i][0] = bufData[0 + i];
426 pchRatio += pchSlope;
427 framePos += pchRatio;
428 bufPos += pchRatio;
430 for (int j = 1; j < inNumSamples; j++){
431 uint32 iBufPos = (uint32)bufPos;
432 float frac = bufPos - (float)iBufPos;
433 int table1 = iBufPos * bufChannels;
434 int table0 = table1 - bufChannels;
435 int table2 = table1 + bufChannels;
436 int table3 = table2 + bufChannels;
437 while(table1 >= bufSamples) table1 -= bufSamples;
438 while(table0 < 0) table0 += bufSamples;
439 while(table2 >= bufSamples) table2 -= bufSamples;
440 while(table3 >= bufSamples) table3 -= bufSamples;
441 for (int i = 0; i < bufChannels; i++){
442 a = bufData[table0 + i];
443 b = bufData[table1 + i];
444 c = bufData[table2 + i];
445 d = bufData[table3 + i];
446 out[i][j] = cubicinterp(frac, a, b, c, d);
448 pchRatio += pchSlope;
449 framePos += pchRatio;
450 oldBufPos = bufPos;
451 bufPos += pchRatio;
452 // the +1 is needed for the cubic interpolation... make SURE the old data isn't needed any more before
453 // setting up the new buffer
454 if ((oldBufPos < (fbufFrames2 + 1)) && ((bufPos >= (fbufFrames2 + 1)))){
455 test = true;
457 if (bufPos >= (fbufFrames + 1)){
458 test = true;
459 bufPos -= fbufFrames;
462 if (unit->m_buf->mask1>=0 && bufPos>=unit->m_buf->mask1) unit->mDone = true;
463 if ( test ){
464 if (unit->m_buf->mask>=0) unit->m_buf->mask1 = unit->m_buf->mask;
465 unit->m_count++;
466 if(unit->mWorld->mRealTime) {
467 test = false;
468 DiskIOMsg msg;
469 msg.mWorld = unit->mWorld;
470 msg.mCommand = (int)ZIN0(2) ? kDiskCmd_ReadLoop : kDiskCmd_Read;
471 msg.mBufNum = (int)fbufnum;
472 uint32 thisPos;
473 if((uint32)bufPos >= bufFrames2) thisPos = 0; else thisPos = bufFrames2;
474 msg.mPos = thisPos;
475 msg.mFrames = bufFrames2;
476 msg.mChannels = bufChannels;
477 gDiskFifo.Write(msg);
478 gDiskFifoHasData.Signal();
481 if((int)ZIN0(3)) {
482 // float outval = bufPos + sc_mod((float)(unit->m_count * bufFrames2), (float)buf->fileinfo.frames);
483 float outval = bufPos + (float)(unit->m_count * bufFrames2);
484 SendNodeReply(&unit->mParent->mNode, (int)ZIN0(3), "/diskin", 1, &outval);
487 } else {
488 SndBuf *bufr = World_GetNRTBuf(unit->mWorld, (int) fbufnum);
489 uint32 mPos;
490 if((uint32)bufPos >= bufFrames2) mPos = 0; else mPos = bufFrames2;
491 if (mPos > (uint32)bufr->frames || mPos + bufFrames2 > (uint32)bufr->frames || (uint32) bufr->channels != bufChannels) return;
492 sf_count_t count;
494 if ((int)ZIN0(2)) { // loop
495 if (!bufr->sndfile) memset(bufr->data + mPos * bufr->channels, 0, bufFrames2 * bufr->channels * sizeof(float));
496 count = sf_readf_float(bufr->sndfile, bufr->data + mPos * bufr->channels, bufFrames2);
497 while (bufFrames2 -= count) {
498 sf_seek(bufr->sndfile, 0, SEEK_SET);
499 count = sf_readf_float(bufr->sndfile, bufr->data + (mPos + count) * bufr->channels, bufFrames2);
501 } else { // non-loop
502 count = bufr->sndfile ? sf_readf_float(bufr->sndfile, bufr->data + mPos * bufr->channels, bufFrames2) : 0;
503 if (count < bufFrames2) {
504 memset(bufr->data + (mPos + count) * bufr->channels, 0, (bufFrames2 - count) * bufr->channels * sizeof(float));
505 unit->m_buf->mask = mPos + count;
512 unit->m_framePos = framePos;
513 unit->m_pchRatio = pchRatio;
514 unit->m_bufPos = bufPos;
517 void VDiskIn_next(VDiskIn *unit, int inNumSamples)
519 float a, b, c, d;
520 bool test = false;
521 double oldBufPos;
523 GET_BUF_SHARED
524 if (!bufData || ((bufFrames & ((unit->mWorld->mBufLength<<1) - 1)) != 0)) {
525 unit->m_framePos = 0.;
526 unit->m_count = 0;
527 ClearUnitOutputs(unit, inNumSamples);
528 return;
531 SETUP_OUT(0)
533 double framePos = unit->m_framePos;
534 double bufPos = unit->m_bufPos; // where we are in the DiskIn buffer
535 float newPchRatio = sc_max(IN0(1), 0.f);
536 if ((newPchRatio * inNumSamples * unit->m_rBufSize) >= 0.5) {
537 printf("pitch ratio is greater then max allowed (see VDiskIn help)\n");
538 ClearUnitOutputs(unit, inNumSamples);
539 return;
542 float pchRatio = unit->m_pchRatio;
543 float pchSlope = CALCSLOPE(newPchRatio, pchRatio);
544 uint32 bufFrames2 = bufFrames >> 1;
545 double fbufFrames2 = (double)bufFrames2;
546 double fbufFrames = (double)bufFrames;
548 for (int j = 0; j < inNumSamples; ++j){
549 int32 iBufPos = (int32)bufPos;
550 double frac = bufPos - (double)iBufPos;
551 int table1 = iBufPos * bufChannels;
552 int table0 = table1 - bufChannels;
553 int table2 = table1 + bufChannels;
554 int table3 = table2 + bufChannels;
555 while(table1 >= bufSamples) table1 -= bufSamples;
556 while(table0 < 0) table0 += bufSamples;
557 while(table2 >= bufSamples) table2 -= bufSamples;
558 while(table3 >= bufSamples) table3 -= bufSamples;
559 for (uint32 i = 0; i < bufChannels; i++){
560 a = bufData[table0 + i];
561 b = bufData[table1 + i];
562 c = bufData[table2 + i];
563 d = bufData[table3 + i];
564 out[i][j] = cubicinterp(frac, a, b, c, d);
566 pchRatio += pchSlope;
567 framePos += pchRatio;
568 oldBufPos = bufPos;
569 bufPos += pchRatio;
571 if ((oldBufPos < (fbufFrames2 + 1)) && ((bufPos >= (fbufFrames2 + 1)))){
572 test = true;
574 if (bufPos >= (fbufFrames + 1)){
575 test = true;
576 bufPos -= fbufFrames;
579 if (unit->m_buf->mask1>=0 && bufPos>=unit->m_buf->mask1) unit->mDone = true;
580 if ( test ){
581 if (unit->m_buf->mask>=0) unit->m_buf->mask1 = unit->m_buf->mask;
582 unit->m_count++;
583 if(unit->mWorld->mRealTime){
584 test = false;
585 DiskIOMsg msg;
586 msg.mWorld = unit->mWorld;
587 msg.mCommand = (int)ZIN0(2) ? kDiskCmd_ReadLoop : kDiskCmd_Read;
588 msg.mBufNum = (int)fbufnum;
589 uint32 thisPos;
590 if((uint32)bufPos >= bufFrames2) thisPos = 0; else thisPos = bufFrames2;
591 msg.mPos = thisPos;
592 msg.mFrames = bufFrames2;
593 msg.mChannels = bufChannels;
594 gDiskFifo.Write(msg);
595 gDiskFifoHasData.Signal();
597 if((int)ZIN0(3)) {
599 // float outval = bufPos + sc_mod((float)(unit->m_count * bufFrames2), (float)buf->fileinfo.frames);
600 float outval = bufPos + (float)(unit->m_count * bufFrames2);
601 SendNodeReply(&unit->mParent->mNode, (int)ZIN0(3), "/diskin", 1, &outval);
604 } else {
605 SndBuf *bufr = World_GetNRTBuf(unit->mWorld, (int)fbufnum);
606 uint32 mPos;
607 if((uint32)bufPos >= bufFrames2) mPos = 0; else mPos = bufFrames2;
608 if (mPos > (uint32)bufr->frames || mPos + bufFrames2 > (uint32)bufr->frames || (uint32) bufr->channels != bufChannels) return;
609 sf_count_t count;
611 if ((int)ZIN0(2)) { // loop
612 if (!bufr->sndfile) memset(bufr->data + mPos * bufr->channels, 0, bufFrames2 * bufr->channels * sizeof(float));
613 count = sf_readf_float(bufr->sndfile, bufr->data + mPos * bufr->channels, bufFrames2);
614 while (bufFrames2 -= count) {
615 sf_seek(bufr->sndfile, 0, SEEK_SET);
616 count = sf_readf_float(bufr->sndfile, bufr->data + (mPos + count) * bufr->channels, bufFrames2);
618 } else { // non-loop
619 count = bufr->sndfile ? sf_readf_float(bufr->sndfile, bufr->data + mPos * bufr->channels, bufFrames2) : 0;
620 if (count < bufFrames2) {
621 memset(bufr->data + (mPos + count) * bufr->channels, 0, (bufFrames2 - count) * bufr->channels * sizeof(float));
622 unit->m_buf->mask = mPos + count;
631 unit->m_framePos = framePos;
632 unit->m_pchRatio = pchRatio;
633 unit->m_bufPos = bufPos;
638 ////////////////////////////////////////////////////////////////////////////////////////////////////////
640 PluginLoad(DiskIO)
642 ft = inTable;
644 #ifdef _WIN32
645 new(&gDiskFifo) MsgFifoNoFree<DiskIOMsg, 256>();
646 new(&gDiskFifoHasData) SC_SyncCondition();
647 #endif
649 pthread_t diskioThread;
650 pthread_create (&diskioThread, NULL, disk_io_thread_func, (void*)0);
652 DefineSimpleUnit(DiskIn);
653 DefineSimpleUnit(DiskOut);
654 DefineSimpleUnit(VDiskIn);
657 //////////////////////////////////////////////////////////////////////////////////////////////////