Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / server / plugins / DiskIO_UGens.cpp
blobb8e4bb8496ecb657762a582ec9c41b1cb41caac9
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"
29 #include <sndfile.h>
31 #include <new>
33 static InterfaceTable *ft;
35 const int kMAXDISKCHANNELS = 32;
37 enum {
38 diskinIdle,
39 diskinStartingEmpty,
40 diskinStartingFull,
41 diskinNormal,
42 diskinLastBuffer,
43 diskinEndSilence
46 enum {
47 diskoutIdle,
48 diskoutNormal
52 struct DiskIn : public Unit
54 float m_fbufnum;
55 SndBuf *m_buf;
56 uint32 m_framepos;
59 struct DiskOut : public Unit
61 float m_fbufnum;
62 SndBuf *m_buf;
63 uint32 m_framepos;
64 uint32 m_framewritten;
67 struct VDiskIn : public Unit
69 float m_fbufnum, m_pchRatio, m_rBufSize;
70 double m_framePos, m_bufPos;
71 uint32 m_count;
72 SndBuf *m_buf;
75 //////////////////////////////////////////////////////////////////////////////////////////////////
77 extern "C"
80 void DiskIn_next(DiskIn *unit, int inNumSamples);
81 void DiskIn_Ctor(DiskIn* unit);
83 void DiskOut_next(DiskOut *unit, int inNumSamples);
84 void DiskOut_Ctor(DiskOut* unit);
86 void VDiskIn_next(VDiskIn *unit, int inNumSamples);
87 void VDiskIn_first(VDiskIn *unit, int inNumSamples);
88 void VDiskIn_Ctor(VDiskIn* unit);
91 //////////////////////////////////////////////////////////////////////////////////////////////////
94 enum {
95 kDiskCmd_Read,
96 kDiskCmd_Write,
97 kDiskCmd_ReadLoop,
100 namespace
103 struct DiskIOMsg
105 World *mWorld;
106 int16 mCommand;
107 int16 mChannels;
108 int32 mBufNum;
109 int32 mPos;
110 int32 mFrames;
112 void Perform();
115 void DiskIOMsg::Perform()
117 NRTLock(mWorld);
119 SndBuf *buf = World_GetNRTBuf(mWorld, mBufNum);
120 if (mPos > buf->frames || mPos + mFrames > buf->frames || buf->channels != mChannels) goto leave;
122 sf_count_t count;
123 switch (mCommand) {
124 case kDiskCmd_Read :
125 count = buf->sndfile ? sf_readf_float(buf->sndfile, buf->data + mPos * buf->channels, mFrames) : 0;
126 if (count < mFrames) {
127 memset(buf->data + (mPos + count) * buf->channels, 0, (mFrames - count) * buf->channels * sizeof(float));
128 World_GetBuf(mWorld, mBufNum)->mask = mPos + count;
129 // NOTE: Possible race condition above: The disk IO thread may write to the rt SndBuf
130 // while the stage3 of the sequenced commands copies the non-rt SndBuf struct to the rt buf.
131 // This only happens if the buffer is modified via an osc command.
132 // We can't use the non-rt SndBuf above since buf->mask won't be reflected to the rt buf.
134 break;
135 case kDiskCmd_ReadLoop :
136 if (!buf->sndfile) {
137 memset(buf->data + mPos * buf->channels, 0, mFrames * buf->channels * sizeof(float));
138 goto leave;
140 count = sf_readf_float(buf->sndfile, buf->data + mPos * buf->channels, mFrames);
141 while (mFrames -= count) {
142 sf_seek(buf->sndfile, 0, SEEK_SET);
143 count = sf_readf_float(buf->sndfile, buf->data + (mPos + count) * buf->channels, mFrames);
145 break;
146 case kDiskCmd_Write :
147 //printf("kDiskCmd_Write %d %p\n", mBufNum, buf->sndfile);
148 if (!buf->sndfile) goto leave;
149 count = sf_writef_float(buf->sndfile, buf->data + mPos * buf->channels, mFrames);
150 break;
153 leave:
154 NRTUnlock(mWorld);
157 MsgFifoNoFree<DiskIOMsg, 256> gDiskFifo;
158 SC_SyncCondition gDiskFifoHasData;
160 void* disk_io_thread_func(void* arg)
162 while (true) {
163 gDiskFifoHasData.WaitEach();
164 gDiskFifo.Perform();
166 return 0;
171 //////////////////////////////////////////////////////////////////////////////////////////////////
173 #define MAXCHANNELS 32
175 #define SETUP_OUT(offset) \
176 if (unit->mNumOutputs != bufChannels) { \
177 ClearUnitOutputs(unit, inNumSamples); \
178 return; \
180 float *out[MAXCHANNELS]; \
181 for (uint32 i=0; i<bufChannels; ++i) out[i] = OUT(i+offset);
183 #define SETUP_IN(offset) \
184 if (unit->mNumInputs - (uint32)offset != bufChannels) { \
185 ClearUnitOutputs(unit, inNumSamples); \
186 return; \
188 float *in[MAXCHANNELS]; \
189 for (uint32 i=0; i<bufChannels; ++i) in[i] = IN(i+offset);
191 void DiskIn_Ctor(DiskIn* unit)
193 unit->m_fbufnum = -1.f;
194 unit->m_buf = unit->mWorld->mSndBufs;
195 unit->m_framepos = 0;
196 SETCALC(DiskIn_next);
199 void DiskIn_next(DiskIn *unit, int inNumSamples)
201 GET_BUF_SHARED
202 if (!bufData || ((bufFrames & ((unit->mWorld->mBufLength<<1) - 1)) != 0)) {
203 unit->m_framepos = 0;
204 ClearUnitOutputs(unit, inNumSamples);
205 return;
207 SETUP_OUT(0)
209 if (unit->m_framepos >= bufFrames) {
210 unit->m_framepos = 0;
213 bufData += unit->m_framepos * bufChannels;
215 // buffer must be allocated as a multiple of 2*blocksize.
216 if (bufChannels > 2) {
217 for (int j=0; j<inNumSamples; ++j) {
218 for (uint32 i=0; i<bufChannels; ++i) {
219 *out[i]++ = *bufData++;
222 } else if (bufChannels == 2) {
223 float *out0 = out[0];
224 float *out1 = out[1];
225 for (int j=0; j<inNumSamples; ++j) {
226 *out0++ = *bufData++;
227 *out1++ = *bufData++;
229 } else {
230 float *out0 = out[0];
231 for (int j=0; j<inNumSamples; ++j) {
232 *out0++ = *bufData++;
235 if(unit->m_buf->mask1>=0 && unit->m_framepos>=unit->m_buf->mask1) unit->mDone = true;
236 unit->m_framepos += inNumSamples;
237 uint32 bufFrames2 = bufFrames >> 1;
238 if (unit->m_framepos == bufFrames) {
239 unit->m_framepos = 0;
240 goto sendMessage;
241 } else if (unit->m_framepos == bufFrames2) {
242 sendMessage:
243 if (unit->m_buf->mask>=0) unit->m_buf->mask1 = unit->m_buf->mask;
244 if(unit->mWorld->mRealTime){
245 // send a message to read
246 DiskIOMsg msg;
247 msg.mWorld = unit->mWorld;
248 msg.mCommand = (int)ZIN0(1) ? kDiskCmd_ReadLoop : kDiskCmd_Read;
249 msg.mBufNum = (int)fbufnum;
250 msg.mPos = bufFrames2 - unit->m_framepos;
251 msg.mFrames = bufFrames2;
252 msg.mChannels = bufChannels;
253 gDiskFifo.Write(msg);
254 gDiskFifoHasData.Signal();
255 } else {
256 SndBuf *bufr = World_GetNRTBuf(unit->mWorld, (int) fbufnum);
257 uint32 mPos = bufFrames2 - unit->m_framepos;
258 if (mPos > (uint32)bufr->frames || mPos + bufFrames2 > (uint32)bufr->frames || (uint32) bufr->channels != bufChannels) return;
259 sf_count_t count;
261 if ((int)ZIN0(1)) { // loop
262 if (!bufr->sndfile) memset(bufr->data + mPos * bufr->channels, 0, bufFrames2 * bufr->channels * sizeof(float));
263 count = sf_readf_float(bufr->sndfile, bufr->data + mPos * bufr->channels, bufFrames2);
264 while (bufFrames2 -= count) {
265 sf_seek(bufr->sndfile, 0, SEEK_SET);
266 count = sf_readf_float(bufr->sndfile, bufr->data + (mPos + count) * bufr->channels, bufFrames2);
268 } else { // non-loop
269 count = bufr->sndfile ? sf_readf_float(bufr->sndfile, bufr->data + mPos * bufr->channels, bufFrames2) : 0;
270 if (count < bufFrames2) {
271 memset(bufr->data + (mPos + count) * bufr->channels, 0, (bufFrames2 - count) * bufr->channels * sizeof(float));
272 unit->m_buf->mask = mPos + count;
279 ////////////////////////////////////////////////////////////////////////////////////////////////////////
281 void DiskOut_Ctor(DiskOut* unit)
283 unit->m_fbufnum = -1.f;
284 unit->m_buf = unit->mWorld->mSndBufs;
285 unit->m_framepos = 0;
286 unit->m_framewritten = 0;
287 SETCALC(DiskOut_next);
291 void DiskOut_next(DiskOut *unit, int inNumSamples)
293 GET_BUF
295 if (!bufData || ((bufFrames & ((unit->mWorld->mBufLength<<1) - 1)) != 0)) {
296 unit->m_framepos = 0;
297 // unit->m_framewritten = 0;
298 return;
300 SETUP_IN(1)
302 float *out = OUT(0);
303 uint32 framew = unit->m_framewritten;
305 // printf("Start frames %i %i\n", unit->m_framewritten, framew );
307 if (unit->m_framepos >= bufFrames) {
308 unit->m_framepos = 0;
311 bufData += unit->m_framepos * bufChannels;
313 if (bufChannels > 2) {
314 for (int j=0; j<inNumSamples; ++j) {
315 for (uint32 i=0; i<bufChannels; ++i) {
316 *bufData++ = *in[i]++;
318 out[j] = framew++;
320 } else if (bufChannels == 2) {
321 float *in0 = in[0];
322 float *in1 = in[1];
323 for (int j=0; j<inNumSamples; ++j) {
324 *bufData++ = *in0++;
325 *bufData++ = *in1++;
326 out[j] = framew++;
328 } else {
329 float *in0 = in[0];
330 for (int j=0; j<inNumSamples; ++j) {
331 *bufData++ = *in0++;
332 out[j] = framew++;
336 unit->m_framepos += inNumSamples;
337 // unit->m_framewritten += inNumSamples;
338 unit->m_framewritten = framew;
340 // printf("frames %i %i\n", unit->m_framewritten, framew );
342 uint32 bufFrames2 = bufFrames >> 1;
343 if (unit->m_framepos == bufFrames) {
344 unit->m_framepos = 0;
345 goto sendMessage;
346 } else if (unit->m_framepos == bufFrames2) {
347 sendMessage:
348 // send a message to write
349 DiskIOMsg msg;
350 msg.mWorld = unit->mWorld;
351 msg.mCommand = kDiskCmd_Write;
352 msg.mBufNum = (int)fbufnum;
353 msg.mPos = bufFrames2 - unit->m_framepos;
354 msg.mFrames = bufFrames2;
355 msg.mChannels = bufChannels;
356 //printf("sendMessage %d %d %d %d\n", msg.mBufNum, msg.mPos, msg.mFrames, msg.mChannels);
357 gDiskFifo.Write(msg);
358 gDiskFifoHasData.Signal();
364 void VDiskIn_Ctor(VDiskIn* unit)
366 unit->m_fbufnum = -1.f;
367 unit->m_buf = unit->mWorld->mSndBufs;
368 unit->m_framePos = 0.;
369 unit->m_bufPos = 0.;
370 unit->m_pchRatio = sc_max(IN0(1), 0.f);
371 unit->m_count = 0;
373 SETCALC(VDiskIn_first); // should be first
376 // first time through, the FIRST sample doesn't need the interpolation... the buffer won't be filled 'correctly' for
377 // interpolation, so use the _first function to make this exception
379 void VDiskIn_first(VDiskIn *unit, int inNumSamples)
382 SETCALC(VDiskIn_next);
384 float a, b, c, d, oldBufPos;
385 bool test = false;
387 GET_BUF_SHARED
389 if (!bufData || ((bufFrames & ((unit->mWorld->mBufLength<<1) - 1)) != 0)) {
390 unit->m_framePos = 0.;
391 unit->m_count = 0;
392 ClearUnitOutputs(unit, inNumSamples);
393 return;
396 uint32 bufFrames2 = bufFrames >> 1;
397 float fbufFrames2 = (float)bufFrames2;
398 float fbufFrames = (float)bufFrames;
399 unit->m_rBufSize = 1. / bufFrames;
401 SETUP_OUT(0)
403 float framePos = unit->m_framePos;
404 float bufPos = unit->m_bufPos; // where we are in the DiskIn buffer
405 float newPchRatio = sc_max(IN0(1), 0.f);
407 if ((newPchRatio * inNumSamples * unit->m_rBufSize) >= 0.5) {
408 printf("pitch ratio is greater then max allowed (see VDiskIn help)\n");
409 ClearUnitOutputs(unit, inNumSamples);
410 SETCALC(VDiskIn_first);
411 return;
414 float pchRatio = unit->m_pchRatio;
415 float pchSlope = CALCSLOPE(newPchRatio, pchRatio);
417 for (uint32 i = 0; i < bufChannels; i++){
418 out[i][0] = bufData[0 + i];
421 pchRatio += pchSlope;
422 framePos += pchRatio;
423 bufPos += pchRatio;
425 for (int j = 1; j < inNumSamples; j++){
426 uint32 iBufPos = (uint32)bufPos;
427 float frac = bufPos - (float)iBufPos;
428 int table1 = iBufPos * bufChannels;
429 int table0 = table1 - bufChannels;
430 int table2 = table1 + bufChannels;
431 int table3 = table2 + bufChannels;
432 while(table1 >= bufSamples) table1 -= bufSamples;
433 while(table0 < 0) table0 += bufSamples;
434 while(table2 >= bufSamples) table2 -= bufSamples;
435 while(table3 >= bufSamples) table3 -= bufSamples;
436 for (int i = 0; i < bufChannels; i++){
437 a = bufData[table0 + i];
438 b = bufData[table1 + i];
439 c = bufData[table2 + i];
440 d = bufData[table3 + i];
441 out[i][j] = cubicinterp(frac, a, b, c, d);
443 pchRatio += pchSlope;
444 framePos += pchRatio;
445 oldBufPos = bufPos;
446 bufPos += pchRatio;
447 // the +1 is needed for the cubic interpolation... make SURE the old data isn't needed any more before
448 // setting up the new buffer
449 if ((oldBufPos < (fbufFrames2 + 1)) && ((bufPos >= (fbufFrames2 + 1)))){
450 test = true;
452 if (bufPos >= (fbufFrames + 1)){
453 test = true;
454 bufPos -= fbufFrames;
457 if (unit->m_buf->mask1>=0 && bufPos>=unit->m_buf->mask1) unit->mDone = true;
458 if ( test ){
459 if (unit->m_buf->mask>=0) unit->m_buf->mask1 = unit->m_buf->mask;
460 unit->m_count++;
461 if(unit->mWorld->mRealTime) {
462 test = false;
463 DiskIOMsg msg;
464 msg.mWorld = unit->mWorld;
465 msg.mCommand = (int)ZIN0(2) ? kDiskCmd_ReadLoop : kDiskCmd_Read;
466 msg.mBufNum = (int)fbufnum;
467 uint32 thisPos;
468 if((uint32)bufPos >= bufFrames2) thisPos = 0; else thisPos = bufFrames2;
469 msg.mPos = thisPos;
470 msg.mFrames = bufFrames2;
471 msg.mChannels = bufChannels;
472 gDiskFifo.Write(msg);
473 gDiskFifoHasData.Signal();
476 if((int)ZIN0(3)) {
477 // float outval = bufPos + sc_mod((float)(unit->m_count * bufFrames2), (float)buf->fileinfo.frames);
478 float outval = bufPos + (float)(unit->m_count * bufFrames2);
479 SendNodeReply(&unit->mParent->mNode, (int)ZIN0(3), "/diskin", 1, &outval);
482 } else {
483 SndBuf *bufr = World_GetNRTBuf(unit->mWorld, (int) fbufnum);
484 uint32 mPos;
485 if((uint32)bufPos >= bufFrames2) mPos = 0; else mPos = bufFrames2;
486 if (mPos > (uint32)bufr->frames || mPos + bufFrames2 > (uint32)bufr->frames || (uint32) bufr->channels != bufChannels) return;
487 sf_count_t count;
489 if ((int)ZIN0(2)) { // loop
490 if (!bufr->sndfile) memset(bufr->data + mPos * bufr->channels, 0, bufFrames2 * bufr->channels * sizeof(float));
491 count = sf_readf_float(bufr->sndfile, bufr->data + mPos * bufr->channels, bufFrames2);
492 while (bufFrames2 -= count) {
493 sf_seek(bufr->sndfile, 0, SEEK_SET);
494 count = sf_readf_float(bufr->sndfile, bufr->data + (mPos + count) * bufr->channels, bufFrames2);
496 } else { // non-loop
497 count = bufr->sndfile ? sf_readf_float(bufr->sndfile, bufr->data + mPos * bufr->channels, bufFrames2) : 0;
498 if (count < bufFrames2) {
499 memset(bufr->data + (mPos + count) * bufr->channels, 0, (bufFrames2 - count) * bufr->channels * sizeof(float));
500 unit->m_buf->mask = mPos + count;
507 unit->m_framePos = framePos;
508 unit->m_pchRatio = pchRatio;
509 unit->m_bufPos = bufPos;
512 void VDiskIn_next(VDiskIn *unit, int inNumSamples)
514 float a, b, c, d;
515 bool test = false;
516 double oldBufPos;
518 GET_BUF_SHARED
519 if (!bufData || ((bufFrames & ((unit->mWorld->mBufLength<<1) - 1)) != 0)) {
520 unit->m_framePos = 0.;
521 unit->m_count = 0;
522 ClearUnitOutputs(unit, inNumSamples);
523 return;
526 SETUP_OUT(0)
528 double framePos = unit->m_framePos;
529 double bufPos = unit->m_bufPos; // where we are in the DiskIn buffer
530 float newPchRatio = sc_max(IN0(1), 0.f);
531 if ((newPchRatio * inNumSamples * unit->m_rBufSize) >= 0.5) {
532 printf("pitch ratio is greater then max allowed (see VDiskIn help)\n");
533 ClearUnitOutputs(unit, inNumSamples);
534 return;
537 float pchRatio = unit->m_pchRatio;
538 float pchSlope = CALCSLOPE(newPchRatio, pchRatio);
539 uint32 bufFrames2 = bufFrames >> 1;
540 double fbufFrames2 = (double)bufFrames2;
541 double fbufFrames = (double)bufFrames;
543 for (int j = 0; j < inNumSamples; ++j){
544 int32 iBufPos = (int32)bufPos;
545 double frac = bufPos - (double)iBufPos;
546 int table1 = iBufPos * bufChannels;
547 int table0 = table1 - bufChannels;
548 int table2 = table1 + bufChannels;
549 int table3 = table2 + bufChannels;
550 while(table1 >= bufSamples) table1 -= bufSamples;
551 while(table0 < 0) table0 += bufSamples;
552 while(table2 >= bufSamples) table2 -= bufSamples;
553 while(table3 >= bufSamples) table3 -= bufSamples;
554 for (uint32 i = 0; i < bufChannels; i++){
555 a = bufData[table0 + i];
556 b = bufData[table1 + i];
557 c = bufData[table2 + i];
558 d = bufData[table3 + i];
559 out[i][j] = cubicinterp(frac, a, b, c, d);
561 pchRatio += pchSlope;
562 framePos += pchRatio;
563 oldBufPos = bufPos;
564 bufPos += pchRatio;
566 if ((oldBufPos < (fbufFrames2 + 1)) && ((bufPos >= (fbufFrames2 + 1)))){
567 test = true;
569 if (bufPos >= (fbufFrames + 1)){
570 test = true;
571 bufPos -= fbufFrames;
574 if (unit->m_buf->mask1>=0 && bufPos>=unit->m_buf->mask1) unit->mDone = true;
575 if ( test ){
576 if (unit->m_buf->mask>=0) unit->m_buf->mask1 = unit->m_buf->mask;
577 unit->m_count++;
578 if(unit->mWorld->mRealTime){
579 test = false;
580 DiskIOMsg msg;
581 msg.mWorld = unit->mWorld;
582 msg.mCommand = (int)ZIN0(2) ? kDiskCmd_ReadLoop : kDiskCmd_Read;
583 msg.mBufNum = (int)fbufnum;
584 uint32 thisPos;
585 if((uint32)bufPos >= bufFrames2) thisPos = 0; else thisPos = bufFrames2;
586 msg.mPos = thisPos;
587 msg.mFrames = bufFrames2;
588 msg.mChannels = bufChannels;
589 gDiskFifo.Write(msg);
590 gDiskFifoHasData.Signal();
592 if((int)ZIN0(3)) {
594 // float outval = bufPos + sc_mod((float)(unit->m_count * bufFrames2), (float)buf->fileinfo.frames);
595 float outval = bufPos + (float)(unit->m_count * bufFrames2);
596 SendNodeReply(&unit->mParent->mNode, (int)ZIN0(3), "/diskin", 1, &outval);
599 } else {
600 SndBuf *bufr = World_GetNRTBuf(unit->mWorld, (int)fbufnum);
601 uint32 mPos;
602 if((uint32)bufPos >= bufFrames2) mPos = 0; else mPos = bufFrames2;
603 if (mPos > (uint32)bufr->frames || mPos + bufFrames2 > (uint32)bufr->frames || (uint32) bufr->channels != bufChannels) return;
604 sf_count_t count;
606 if ((int)ZIN0(2)) { // loop
607 if (!bufr->sndfile) memset(bufr->data + mPos * bufr->channels, 0, bufFrames2 * bufr->channels * sizeof(float));
608 count = sf_readf_float(bufr->sndfile, bufr->data + mPos * bufr->channels, bufFrames2);
609 while (bufFrames2 -= count) {
610 sf_seek(bufr->sndfile, 0, SEEK_SET);
611 count = sf_readf_float(bufr->sndfile, bufr->data + (mPos + count) * bufr->channels, bufFrames2);
613 } else { // non-loop
614 count = bufr->sndfile ? sf_readf_float(bufr->sndfile, bufr->data + mPos * bufr->channels, bufFrames2) : 0;
615 if (count < bufFrames2) {
616 memset(bufr->data + (mPos + count) * bufr->channels, 0, (bufFrames2 - count) * bufr->channels * sizeof(float));
617 unit->m_buf->mask = mPos + count;
626 unit->m_framePos = framePos;
627 unit->m_pchRatio = pchRatio;
628 unit->m_bufPos = bufPos;
633 ////////////////////////////////////////////////////////////////////////////////////////////////////////
635 PluginLoad(DiskIO)
637 ft = inTable;
639 #ifdef _WIN32
640 new(&gDiskFifo) MsgFifoNoFree<DiskIOMsg, 256>();
641 new(&gDiskFifoHasData) SC_SyncCondition();
642 #endif
644 pthread_t diskioThread;
645 pthread_create (&diskioThread, NULL, disk_io_thread_func, (void*)0);
647 DefineSimpleUnit(DiskIn);
648 DefineSimpleUnit(DiskOut);
649 DefineSimpleUnit(VDiskIn);
652 //////////////////////////////////////////////////////////////////////////////////////////////////