/* * Copyright (c) 1983-2023 Richard Dobson and Composers Desktop Project Ltd * http://www.rwdobson.com * http://www.composersdesktop.com * This file is part of the CDP System. * The CDP System is free software; you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * The CDP System is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with the CDP System; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* pvthreads.c: thread functions for pvplay */ /* Jan 2014: completed pvx support */ #include "pvplay.h" #include extern int file_playing; extern psfdata *g_pdata; __inline void stereo_interls(const float *in_l,const float *in_r,float *out,long insize) { long i; const float *pfl_l,*pfl_r; float*pfl_o; pfl_o = out; pfl_l = in_l; pfl_r = in_r; for(i=insize;i;--i){ *pfl_o++ = *pfl_l++; *pfl_o++ = *pfl_r++; } } /* soundfile */ /* writes decoded data to ring buffer */ /* size of element is full m/c frame */ // TODO: unix thread should return void* #ifdef unix /*int*/void* threadFunctionReadFromRawFile(void* ptr) #else unsigned int __stdcall threadFunctionReadFromRawFile(void* ptr) #endif { psfdata* pdata = (psfdata*)ptr; /* Mark thread started */ pdata->flag = 0; //printf("thread: from_frame = %d, to_frame = %d, current_Frame = %d\n",pdata->from_frame, pdata->to_frame, pdata->current_frame); while (1) { // ring_buffer_size_t nElementsProcessed = 0; ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferWriteAvailable(&pdata->ringbuf); if (elementsInBuffer >= pdata->ringbuf.bufferSize / 4) { void* ptr[2] = {NULL}; ring_buffer_size_t sizes[2] = {0}; /* By using PaUtil_GetRingBufferWriteRegions, we can write directly into the ring buffer */ PaUtil_GetRingBufferWriteRegions(&pdata->ringbuf, elementsInBuffer, &ptr[0], &sizes[0], &ptr[1], &sizes[1]); if (file_playing) { ring_buffer_size_t itemsReadFromFile; int i,j; int framesread = 0; int sampswanted = 0; int sampsread = 0; // we work with frames, = constant across inchans and outchans itemsReadFromFile = 0; for(i = 0; i < 2 && (ptr[i] != NULL); i++) { // NB ringbuf is sized by m/c frames int frameswanted = sizes[i]; pdata->current_frame = sndtellEx(pdata->ifd) / pdata->inchans; // read up to end frame if requested if(pdata->to_frame < pdata->current_frame + frameswanted) frameswanted = pdata->to_frame - pdata->current_frame; if(frameswanted > 0){ sampswanted = frameswanted * pdata->inchans; sampsread = fgetfbufEx(pdata->inbuf,sampswanted,pdata->ifd,0); framesread = sampsread / pdata->inchans; if(framesread < 0){ // read error! printf("Error reading soundfile: %s ifd = %d\n", sferrstr(),pdata->ifd); pdata->flag = 1; file_playing = 0; break; // just out of for loop - need to return instead? } } if(framesread == 0){ /* EOF. EITHER: finish, or rewind if looping playback*/ if(pdata->play_looped){ if(sndseekEx(pdata->ifd,pdata->from_frame * pdata->inchans,SEEK_SET) < 0){ printf("Error looping soundfile\n"); pdata->flag = 1; file_playing = 0; break; } // sizes[1] especially may well = 0 if(frameswanted==0) break; sampswanted = frameswanted * pdata->inchans; sampsread = fgetfbufEx(pdata->inbuf,sampswanted,pdata->ifd,0); framesread = sampsread / pdata->inchans; if(framesread < 0){ // read error! printf("Error reading soundfile\n"); pdata->flag = 1; file_playing = 0; break; } } else { // we must watch the ring buffer to make sure all data has been rendered, // over several callback blocks //printf("End of data. playing = %d:\n", file_playing); //printf("\telements remaining = %d\n",elementsInBuffer); if(elementsInBuffer == pdata->ringbuf.bufferSize) { pdata->flag = 1; break; } } } else { // ringbuf calcs always relative to outchans itemsReadFromFile += framesread; // now ready to apply decoding or other processing if(pdata->gain != 1.0){ for(j = 0; j < framesread * pdata->inchans; j++) pdata->inbuf[j] = (float)(pdata->inbuf[j] * pdata->gain); } // no channel mapping in pvplay if(pdata->do_decode) { ABFSAMPLE abfsamp; for(j=0; j < framesread; j++){ // single frame only pdata->copyfunc(&abfsamp,pdata->inbuf + (j * pdata->inchans)); /* BIG TODO: update funcs to process large frame buffer! */ /* NB: ring buffer is effectively defined as raw bytes */ pdata->decodefunc(&abfsamp, (float*)(ptr[i]) + (j * pdata->outchans), 1); // nElementsProcessed++; } } else { // inchans = outchans memcpy(ptr[i],pdata->inbuf,framesread * sizeof(float) * pdata->inchans); // nElementsProcessed += framesread; } } } PaUtil_AdvanceRingBufferWriteIndex(&pdata->ringbuf, itemsReadFromFile); } else { // this code is activated on Ctrl-C. Can do immediate finish by setting flag // printf("file done\n"); pdata->flag = 1; break; } } // else { // printf("ringbuf size = %d, elements remaining = %d, playing = %d\n",pdata->ringbuf.bufferSize,elementsInBuffer,file_playing); // } /* Sleep a little while...! */ Pa_Sleep(10); } return 0; } /* versions for .ana file (always mono only) */ #ifdef unix /*int*/void* threadFunctionReadFromAnalFile(void* ptr) #else unsigned int __stdcall threadFunctionReadFromAnalFile(void* ptr) #endif { psfdata* pdata = (psfdata*)ptr; /* Mark thread started */ pdata->flag = 0; while (1) { // ring_buffer_size_t nElementsProcessed = 0; ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferWriteAvailable(&pdata->ringbuf); if (elementsInBuffer >= pdata->ringbuf.bufferSize / 4) { void* ptr[2] = {NULL}; ring_buffer_size_t sizes[2] = {0}; //read and analyse as many frames as resynth space is available for, into inbuf // then distribute to ringbuf sections as necessary //printf("from_frame = %d,to_frame = %d\n",pdata->from_frame,pdata->to_frame); int framestoget = elementsInBuffer / pdata->fftsize; //assert(framestoget); // must be at least one, or ring buffer is too small! if(framestoget==0) break; float *pbuf = pdata->inbuf; int i, got = 0; int elementstowrite = 0; if (file_playing) { /* find out where we are ... */ // unsigned: we have to trust no error from sndtellEx! unsigned long framepos1 = (unsigned long) (sndtellEx(pdata->ifd) / pdata->anal_framesize); for(i = 0;i < framestoget;i++){ /* read one frame*/ got = fgetfbufEx(pdata->analframe1,pdata->anal_framesize,pdata->ifd,0); if(got == -1) { fprintf(stderr,"\nError reading from sfile\n"); file_playing = 0; break; } if(got != pdata->anal_framesize) { if(got > 0) { fprintf(stderr,"Infile error: incomplete analysis frame encountered\n"); file_playing = 0; break; } } framepos1++; if(got && (framepos1 <= pdata->to_frame)) { long samps = pdata->pv_l->process_frame(pdata->analframe1,pbuf,PVOC_AMP_FREQ); elementstowrite += samps; pbuf += samps; } if (got==0 || framepos1 >= pdata->to_frame){ if(pdata->play_looped) { long pos; pos = sndseekEx(pdata->ifd,pdata->from_frame * pdata->anal_framesize,SEEK_SET); if(pos < 0){ fprintf(stderr,"Error rewinding frame loop.\n"); file_playing = 0; break; } } else if(elementsInBuffer == pdata->ringbuf.bufferSize) { //printf("buffer empty!\n"); pdata->flag = 1; break; } } } //apply gain to inbuf first, then copy to ringbuf if(pdata->gain != 1.0) { for(i=0;i < elementstowrite;i++) pdata->inbuf[i] = (float)(pdata->inbuf[i] * pdata->gain); } /* By using PaUtil_GetRingBufferWriteRegions, we can write directly into the ring buffer */ PaUtil_GetRingBufferWriteRegions(&pdata->ringbuf, elementsInBuffer, &ptr[0], &sizes[0], &ptr[1], &sizes[1]); pbuf = pdata->inbuf; //printf("ring buffer sizes : %d:%d",(int)sizes[0], (int)sizes[1]); for(i = 0; i < 2; i++) { if(ptr[i]) { int frameswanted = sizes[i]; memcpy(ptr[i],pbuf,frameswanted * sizeof(float) * pdata->inchans); pbuf += frameswanted * pdata->inchans; // nElementsProcessed += frameswanted; } } if(elementstowrite) PaUtil_AdvanceRingBufferWriteIndex(&pdata->ringbuf, elementstowrite); } // file_playing else { // this code is activated on Ctrl-C. Can do immediate finish by setting flag // printf("file done\n"); pdata->flag = 1; break; } } /* Sleep a little while... */ Pa_Sleep(5); } return 0; } // version for pvx format, just mono and stereo supported for now #ifdef unix /*int*/void* threadFunctionReadFromPVXFile(void* ptr) #else unsigned int __stdcall threadFunctionReadFromPVXFile(void* ptr) #endif { psfdata* pdata = (psfdata*)ptr; /* Mark thread started */ pdata->flag = 0; while (1) { // ring_buffer_size_t nElementsProcessed = 0; ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferWriteAvailable(&pdata->ringbuf); // we remember that an element is a frame (mono or stereo) if (elementsInBuffer >= pdata->ringbuf.bufferSize / 4) { void* ptr[2] = {NULL}; ring_buffer_size_t sizes[2] = {0}; //read and analyse as many frames as resynth space is available for, into inbuf // then distribute to ringbuf sections as necessary //printf("from_frame = %d,to_frame = %d\n",pdata->from_frame,pdata->to_frame); /* pvx : file framecount is of single frames - need enough space to synth a single mc analysis frame */ /* but to_frame and from_frame are m/c frame count */ int framestoget = (elementsInBuffer / pdata->fftsize); if(framestoget==0) break; int i, got = 0; int elementstowrite = 0; if(pdata->inchans==1) { float *pbuf = pdata->inbuf; if (file_playing) { /* find out where we are ...multi-chan frame count */ long framepos = pvoc_framepos(pdata->pvfile) / pdata->inchans; // NB if(framepos < 0){ fprintf(stderr,"\nError reading file frame position\n"); file_playing = 0; break; } for(i = 0;i < framestoget;i++){ /* read one frame*/ got = pvoc_getframes(pdata->pvfile,pdata->analframe1,1); if(got == -1) { fprintf(stderr,"\nError reading from sfile\n"); file_playing = 0; break; } framepos++; if(got && (framepos <= (long) pdata->to_frame)) { long samps = pdata->pv_l->process_frame(pdata->analframe1,pbuf,PVOC_AMP_FREQ); elementstowrite += samps; pbuf += samps; } if (got==0 || framepos >= (long) pdata->to_frame){ if(pdata->play_looped) { if(pvoc_seek_mcframe(pdata->pvfile,pdata->from_frame,SEEK_SET)) { fprintf(stderr,"Error rewinding frame loop.\n"); file_playing = 0; break; } } else if(elementsInBuffer == pdata->ringbuf.bufferSize) { //printf("buffer empty!\n"); pdata->flag = 1; break; } } } // framestoget //apply gain to inbuf first, then copy to ringbuf if(pdata->gain != 1.0) { for(i=0;i < elementstowrite;i++) pdata->inbuf[i] = (float)(pdata->inbuf[i] * pdata->gain); } /* By using PaUtil_GetRingBufferWriteRegions, we can write directly into the ring buffer */ PaUtil_GetRingBufferWriteRegions(&pdata->ringbuf, elementsInBuffer, &ptr[0], &sizes[0], &ptr[1], &sizes[1]); pbuf = pdata->inbuf; //printf("ring buffer sizes : %d:%d",(int)sizes[0], (int)sizes[1]); for(i = 0; i < 2; i++) { if(ptr[i]) { int frameswanted = sizes[i]; memcpy(ptr[i],pbuf,frameswanted * sizeof(float) * pdata->inchans); pbuf += frameswanted * pdata->inchans; // nElementsProcessed += frameswanted; } } if(elementstowrite) PaUtil_AdvanceRingBufferWriteIndex(&pdata->ringbuf, elementstowrite); } // file_playing else { // this code is activated on Ctrl-C. Can do immediate finish by setting flag // printf("file done\n"); pdata->flag = 1; break; } } //inchans==1 else if (pdata->inchans==2) { float *pbuf = pdata->outbuf_l; float *pbuf_r = pdata->outbuf_r; // framestoget is count of m/c analysis frames to read if(file_playing){ /* find out where we are ...multi-chan frame count */ long framepos = pvoc_framepos(pdata->pvfile) / pdata->inchans; // NB if(framepos < 0){ fprintf(stderr,"\nError reading file frame position\n"); file_playing = 0; break; } for(i = 0;i < framestoget;i++){ /* read one stereo frame*/ got = pvoc_getframes(pdata->pvfile,pdata->analframe1,1); if(got == -1) { fprintf(stderr,"\nError reading ch 1 from pvx file\n"); file_playing = 0; break; } if(got){ got = pvoc_getframes(pdata->pvfile,pdata->analframe2,1); if(got == -1) { fprintf(stderr,"\nError reading ch 2 from pvx file, frame %lu\n",framepos); file_playing = 0; break; } } framepos++; if(got && (framepos <= (long) pdata->to_frame)) { // each call returns samples long samps = pdata->pv_l->process_frame(pdata->analframe1,pbuf,PVOC_AMP_FREQ); pbuf += samps; samps = pdata->pv_r->process_frame(pdata->analframe2,pbuf_r,PVOC_AMP_FREQ); pbuf_r += samps; elementstowrite += samps; // element is a stereo sample frame } if (got==0 || framepos >= (long) pdata->to_frame){ if(pdata->play_looped) { //pos = sndseekEx(pdata->ifd,pdata->from_frame * pdata->anal_framesize,SEEK_SET); //if(pvoc_rewind(pdata->pvfile,1)){ /* 1 = skip empty frame */ if(pvoc_seek_mcframe(pdata->pvfile,pdata->from_frame,SEEK_SET)) { fprintf(stderr,"Error rewinding frame loop.\n"); file_playing = 0; break; } } else if(elementsInBuffer == pdata->ringbuf.bufferSize) { //printf("buffer empty!\n"); pdata->flag = 1; break; } } } // framestogeet assert(elementstowrite <= elementsInBuffer); //apply gain to inbuf first, then copy to ringbuf pbuf = pdata->outbuf_l; pbuf_r = pdata->outbuf_r; stereo_interls(pbuf,pbuf_r,pdata->inbuf,elementstowrite); if(pdata->gain != 1.0) { for(i=0;i < elementstowrite * pdata->inchans;i++) pdata->inbuf[i] = (float)(pdata->inbuf[i] * pdata->gain); } /* By using PaUtil_GetRingBufferWriteRegions, we can write directly into the ring buffer */ PaUtil_GetRingBufferWriteRegions(&pdata->ringbuf, elementsInBuffer, &ptr[0], &sizes[0], &ptr[1], &sizes[1]); pbuf = pdata->inbuf; //printf("ring buffer sizes : %d:%d",(int)sizes[0], (int)sizes[1]); for(i = 0; i < 2; i++) { if(ptr[i]) { int frameswanted = sizes[i]; memcpy(ptr[i],pbuf,frameswanted * sizeof(float) * pdata->inchans); pbuf += frameswanted * pdata->inchans; // nElementsProcessed += frameswanted; } } if(elementstowrite) PaUtil_AdvanceRingBufferWriteIndex(&pdata->ringbuf, elementstowrite); } // file_playing else { pdata->flag = 1; break; } } } /* Sleep a little while... */ Pa_Sleep(5); } return 0; }