gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

paex_record_file.c (15810B)


      1 /** @file paex_record_file.c
      2 	@ingroup examples_src
      3 	@brief Record input into a file, then playback recorded data from file (Windows only at the moment) 
      4 	@author Robert Bielik
      5 */
      6 /*
      7  * $Id: paex_record_file.c 1752 2011-09-08 03:21:55Z philburk $
      8  *
      9  * This program uses the PortAudio Portable Audio Library.
     10  * For more information see: http://www.portaudio.com
     11  * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
     12  *
     13  * Permission is hereby granted, free of charge, to any person obtaining
     14  * a copy of this software and associated documentation files
     15  * (the "Software"), to deal in the Software without restriction,
     16  * including without limitation the rights to use, copy, modify, merge,
     17  * publish, distribute, sublicense, and/or sell copies of the Software,
     18  * and to permit persons to whom the Software is furnished to do so,
     19  * subject to the following conditions:
     20  *
     21  * The above copyright notice and this permission notice shall be
     22  * included in all copies or substantial portions of the Software.
     23  *
     24  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     25  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     26  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     27  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
     28  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
     29  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
     30  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     31  */
     32 
     33 /*
     34  * The text above constitutes the entire PortAudio license; however, 
     35  * the PortAudio community also makes the following non-binding requests:
     36  *
     37  * Any person wishing to distribute modifications to the Software is
     38  * requested to send the modifications to the original developer so that
     39  * they can be incorporated into the canonical version. It is also 
     40  * requested that these non-binding requests be included along with the 
     41  * license above.
     42  */
     43 
     44 #include <stdio.h>
     45 #include <stdlib.h>
     46 #include "portaudio.h"
     47 #include "pa_ringbuffer.h"
     48 #include "pa_util.h"
     49 
     50 #ifdef _WIN32
     51 #include <windows.h>
     52 #include <process.h>
     53 #endif
     54 
     55 static ring_buffer_size_t rbs_min(ring_buffer_size_t a, ring_buffer_size_t b)
     56 {
     57     return (a < b) ? a : b;
     58 }
     59 
     60 /* #define SAMPLE_RATE  (17932) // Test failure to open with this value. */
     61 #define FILE_NAME       "audio_data.raw"
     62 #define SAMPLE_RATE  (44100)
     63 #define FRAMES_PER_BUFFER (512)
     64 #define NUM_SECONDS     (10)
     65 #define NUM_CHANNELS    (2)
     66 #define NUM_WRITES_PER_BUFFER   (4)
     67 /* #define DITHER_FLAG     (paDitherOff) */
     68 #define DITHER_FLAG     (0) /**/
     69 
     70 
     71 /* Select sample format. */
     72 #if 1
     73 #define PA_SAMPLE_TYPE  paFloat32
     74 typedef float SAMPLE;
     75 #define SAMPLE_SILENCE  (0.0f)
     76 #define PRINTF_S_FORMAT "%.8f"
     77 #elif 1
     78 #define PA_SAMPLE_TYPE  paInt16
     79 typedef short SAMPLE;
     80 #define SAMPLE_SILENCE  (0)
     81 #define PRINTF_S_FORMAT "%d"
     82 #elif 0
     83 #define PA_SAMPLE_TYPE  paInt8
     84 typedef char SAMPLE;
     85 #define SAMPLE_SILENCE  (0)
     86 #define PRINTF_S_FORMAT "%d"
     87 #else
     88 #define PA_SAMPLE_TYPE  paUInt8
     89 typedef unsigned char SAMPLE;
     90 #define SAMPLE_SILENCE  (128)
     91 #define PRINTF_S_FORMAT "%d"
     92 #endif
     93 
     94 typedef struct
     95 {
     96     unsigned            frameIndex;
     97     int                 threadSyncFlag;
     98     SAMPLE             *ringBufferData;
     99     PaUtilRingBuffer    ringBuffer;
    100     FILE               *file;
    101     void               *threadHandle;
    102 }
    103 paTestData;
    104 
    105 /* This routine is run in a separate thread to write data from the ring buffer into a file (during Recording) */
    106 static int threadFunctionWriteToRawFile(void* ptr)
    107 {
    108     paTestData* pData = (paTestData*)ptr;
    109 
    110     /* Mark thread started */
    111     pData->threadSyncFlag = 0;
    112 
    113     while (1)
    114     {
    115         ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferReadAvailable(&pData->ringBuffer);
    116         if ( (elementsInBuffer >= pData->ringBuffer.bufferSize / NUM_WRITES_PER_BUFFER) ||
    117              pData->threadSyncFlag )
    118         {
    119             void* ptr[2] = {0};
    120             ring_buffer_size_t sizes[2] = {0};
    121 
    122             /* By using PaUtil_GetRingBufferReadRegions, we can read directly from the ring buffer */
    123             ring_buffer_size_t elementsRead = PaUtil_GetRingBufferReadRegions(&pData->ringBuffer, elementsInBuffer, ptr + 0, sizes + 0, ptr + 1, sizes + 1);
    124             if (elementsRead > 0)
    125             {
    126                 int i;
    127                 for (i = 0; i < 2 && ptr[i] != NULL; ++i)
    128                 {
    129                     fwrite(ptr[i], pData->ringBuffer.elementSizeBytes, sizes[i], pData->file);
    130                 }
    131                 PaUtil_AdvanceRingBufferReadIndex(&pData->ringBuffer, elementsRead);
    132             }
    133 
    134             if (pData->threadSyncFlag)
    135             {
    136                 break;
    137             }
    138         }
    139 
    140         /* Sleep a little while... */
    141         Pa_Sleep(20);
    142     }
    143 
    144     pData->threadSyncFlag = 0;
    145 
    146     return 0;
    147 }
    148 
    149 /* This routine is run in a separate thread to read data from file into the ring buffer (during Playback). When the file
    150    has reached EOF, a flag is set so that the play PA callback can return paComplete */
    151 static int threadFunctionReadFromRawFile(void* ptr)
    152 {
    153     paTestData* pData = (paTestData*)ptr;
    154 
    155     while (1)
    156     {
    157         ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferWriteAvailable(&pData->ringBuffer);
    158 
    159         if (elementsInBuffer >= pData->ringBuffer.bufferSize / NUM_WRITES_PER_BUFFER)
    160         {
    161             void* ptr[2] = {0};
    162             ring_buffer_size_t sizes[2] = {0};
    163 
    164             /* By using PaUtil_GetRingBufferWriteRegions, we can write directly into the ring buffer */
    165             PaUtil_GetRingBufferWriteRegions(&pData->ringBuffer, elementsInBuffer, ptr + 0, sizes + 0, ptr + 1, sizes + 1);
    166 
    167             if (!feof(pData->file))
    168             {
    169                 ring_buffer_size_t itemsReadFromFile = 0;
    170                 int i;
    171                 for (i = 0; i < 2 && ptr[i] != NULL; ++i)
    172                 {
    173                     itemsReadFromFile += (ring_buffer_size_t)fread(ptr[i], pData->ringBuffer.elementSizeBytes, sizes[i], pData->file);
    174                 }
    175                 PaUtil_AdvanceRingBufferWriteIndex(&pData->ringBuffer, itemsReadFromFile);
    176 
    177                 /* Mark thread started here, that way we "prime" the ring buffer before playback */
    178                 pData->threadSyncFlag = 0;
    179             }
    180             else
    181             {
    182                 /* No more data to read */
    183                 pData->threadSyncFlag = 1;
    184                 break;
    185             }
    186         }
    187 
    188         /* Sleep a little while... */
    189         Pa_Sleep(20);
    190     }
    191 
    192     return 0;
    193 }
    194 
    195 typedef int (*ThreadFunctionType)(void*);
    196 
    197 /* Start up a new thread in the given function, at the moment only Windows, but should be very easy to extend
    198    to posix type OSs (Linux/Mac) */
    199 static PaError startThread( paTestData* pData, ThreadFunctionType fn )
    200 {
    201 #ifdef _WIN32
    202     typedef unsigned (__stdcall* WinThreadFunctionType)(void*);
    203     pData->threadHandle = (void*)_beginthreadex(NULL, 0, (WinThreadFunctionType)fn, pData, CREATE_SUSPENDED, NULL);
    204     if (pData->threadHandle == NULL) return paUnanticipatedHostError;
    205 
    206     /* Set file thread to a little higher prio than normal */
    207     SetThreadPriority(pData->threadHandle, THREAD_PRIORITY_ABOVE_NORMAL);
    208 
    209     /* Start it up */
    210     pData->threadSyncFlag = 1;
    211     ResumeThread(pData->threadHandle);
    212 
    213 #endif
    214 
    215     /* Wait for thread to startup */
    216     while (pData->threadSyncFlag) {
    217         Pa_Sleep(10);
    218     }
    219 
    220     return paNoError;
    221 }
    222 
    223 static int stopThread( paTestData* pData )
    224 {
    225     pData->threadSyncFlag = 1;
    226     /* Wait for thread to stop */
    227     while (pData->threadSyncFlag) {
    228         Pa_Sleep(10);
    229     }
    230 #ifdef _WIN32
    231     CloseHandle(pData->threadHandle);
    232     pData->threadHandle = 0;
    233 #endif
    234 
    235     return paNoError;
    236 }
    237 
    238 
    239 /* This routine will be called by the PortAudio engine when audio is needed.
    240 ** It may be called at interrupt level on some machines so don't do anything
    241 ** that could mess up the system like calling malloc() or free().
    242 */
    243 static int recordCallback( const void *inputBuffer, void *outputBuffer,
    244                            unsigned long framesPerBuffer,
    245                            const PaStreamCallbackTimeInfo* timeInfo,
    246                            PaStreamCallbackFlags statusFlags,
    247                            void *userData )
    248 {
    249     paTestData *data = (paTestData*)userData;
    250     ring_buffer_size_t elementsWriteable = PaUtil_GetRingBufferWriteAvailable(&data->ringBuffer);
    251     ring_buffer_size_t elementsToWrite = rbs_min(elementsWriteable, (ring_buffer_size_t)(framesPerBuffer * NUM_CHANNELS));
    252     const SAMPLE *rptr = (const SAMPLE*)inputBuffer;
    253 
    254     (void) outputBuffer; /* Prevent unused variable warnings. */
    255     (void) timeInfo;
    256     (void) statusFlags;
    257     (void) userData;
    258 
    259     data->frameIndex += PaUtil_WriteRingBuffer(&data->ringBuffer, rptr, elementsToWrite);
    260 
    261     return paContinue;
    262 }
    263 
    264 /* This routine will be called by the PortAudio engine when audio is needed.
    265 ** It may be called at interrupt level on some machines so don't do anything
    266 ** that could mess up the system like calling malloc() or free().
    267 */
    268 static int playCallback( const void *inputBuffer, void *outputBuffer,
    269                          unsigned long framesPerBuffer,
    270                          const PaStreamCallbackTimeInfo* timeInfo,
    271                          PaStreamCallbackFlags statusFlags,
    272                          void *userData )
    273 {
    274     paTestData *data = (paTestData*)userData;
    275     ring_buffer_size_t elementsToPlay = PaUtil_GetRingBufferReadAvailable(&data->ringBuffer);
    276     ring_buffer_size_t elementsToRead = rbs_min(elementsToPlay, (ring_buffer_size_t)(framesPerBuffer * NUM_CHANNELS));
    277     SAMPLE* wptr = (SAMPLE*)outputBuffer;
    278 
    279     (void) inputBuffer; /* Prevent unused variable warnings. */
    280     (void) timeInfo;
    281     (void) statusFlags;
    282     (void) userData;
    283 
    284     data->frameIndex += PaUtil_ReadRingBuffer(&data->ringBuffer, wptr, elementsToRead);
    285 
    286     return data->threadSyncFlag ? paComplete : paContinue;
    287 }
    288 
    289 static unsigned NextPowerOf2(unsigned val)
    290 {
    291     val--;
    292     val = (val >> 1) | val;
    293     val = (val >> 2) | val;
    294     val = (val >> 4) | val;
    295     val = (val >> 8) | val;
    296     val = (val >> 16) | val;
    297     return ++val;
    298 }
    299 
    300 /*******************************************************************/
    301 int main(void);
    302 int main(void)
    303 {
    304     PaStreamParameters  inputParameters,
    305                         outputParameters;
    306     PaStream*           stream;
    307     PaError             err = paNoError;
    308     paTestData          data = {0};
    309     unsigned            delayCntr;
    310     unsigned            numSamples;
    311     unsigned            numBytes;
    312 
    313     printf("patest_record.c\n"); fflush(stdout);
    314 
    315     /* We set the ring buffer size to about 500 ms */
    316     numSamples = NextPowerOf2((unsigned)(SAMPLE_RATE * 0.5 * NUM_CHANNELS));
    317     numBytes = numSamples * sizeof(SAMPLE);
    318     data.ringBufferData = (SAMPLE *) PaUtil_AllocateMemory( numBytes );
    319     if( data.ringBufferData == NULL )
    320     {
    321         printf("Could not allocate ring buffer data.\n");
    322         goto done;
    323     }
    324 
    325     if (PaUtil_InitializeRingBuffer(&data.ringBuffer, sizeof(SAMPLE), numSamples, data.ringBufferData) < 0)
    326     {
    327         printf("Failed to initialize ring buffer. Size is not power of 2 ??\n");
    328         goto done;
    329     }
    330 
    331     err = Pa_Initialize();
    332     if( err != paNoError ) goto done;
    333 
    334     inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
    335     if (inputParameters.device == paNoDevice) {
    336         fprintf(stderr,"Error: No default input device.\n");
    337         goto done;
    338     }
    339     inputParameters.channelCount = 2;                    /* stereo input */
    340     inputParameters.sampleFormat = PA_SAMPLE_TYPE;
    341     inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
    342     inputParameters.hostApiSpecificStreamInfo = NULL;
    343 
    344     /* Record some audio. -------------------------------------------- */
    345     err = Pa_OpenStream(
    346               &stream,
    347               &inputParameters,
    348               NULL,                  /* &outputParameters, */
    349               SAMPLE_RATE,
    350               FRAMES_PER_BUFFER,
    351               paClipOff,      /* we won't output out of range samples so don't bother clipping them */
    352               recordCallback,
    353               &data );
    354     if( err != paNoError ) goto done;
    355 
    356     /* Open the raw audio 'cache' file... */
    357     data.file = fopen(FILE_NAME, "wb");
    358     if (data.file == 0) goto done;
    359 
    360     /* Start the file writing thread */
    361     err = startThread(&data, threadFunctionWriteToRawFile);
    362     if( err != paNoError ) goto done;
    363 
    364     err = Pa_StartStream( stream );
    365     if( err != paNoError ) goto done;
    366     printf("\n=== Now recording to '" FILE_NAME "' for %d seconds!! Please speak into the microphone. ===\n", NUM_SECONDS); fflush(stdout);
    367 
    368     /* Note that the RECORDING part is limited with TIME, not size of the file and/or buffer, so you can
    369        increase NUM_SECONDS until you run out of disk */
    370     delayCntr = 0;
    371     while( delayCntr++ < NUM_SECONDS )
    372     {
    373         printf("index = %d\n", data.frameIndex ); fflush(stdout);
    374         Pa_Sleep(1000);
    375     }
    376     if( err < 0 ) goto done;
    377 
    378     err = Pa_CloseStream( stream );
    379     if( err != paNoError ) goto done;
    380 
    381     /* Stop the thread */
    382     err = stopThread(&data);
    383     if( err != paNoError ) goto done;
    384 
    385     /* Close file */
    386     fclose(data.file);
    387     data.file = 0;
    388 
    389     /* Playback recorded data.  -------------------------------------------- */
    390     data.frameIndex = 0;
    391 
    392     outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
    393     if (outputParameters.device == paNoDevice) {
    394         fprintf(stderr,"Error: No default output device.\n");
    395         goto done;
    396     }
    397     outputParameters.channelCount = 2;                     /* stereo output */
    398     outputParameters.sampleFormat =  PA_SAMPLE_TYPE;
    399     outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
    400     outputParameters.hostApiSpecificStreamInfo = NULL;
    401 
    402     printf("\n=== Now playing back from file '" FILE_NAME "' until end-of-file is reached ===\n"); fflush(stdout);
    403     err = Pa_OpenStream(
    404               &stream,
    405               NULL, /* no input */
    406               &outputParameters,
    407               SAMPLE_RATE,
    408               FRAMES_PER_BUFFER,
    409               paClipOff,      /* we won't output out of range samples so don't bother clipping them */
    410               playCallback,
    411               &data );
    412     if( err != paNoError ) goto done;
    413 
    414     if( stream )
    415     {
    416         /* Open file again for reading */
    417         data.file = fopen(FILE_NAME, "rb");
    418         if (data.file != 0)
    419         {
    420             /* Start the file reading thread */
    421             err = startThread(&data, threadFunctionReadFromRawFile);
    422             if( err != paNoError ) goto done;
    423 
    424             err = Pa_StartStream( stream );
    425             if( err != paNoError ) goto done;
    426 
    427             printf("Waiting for playback to finish.\n"); fflush(stdout);
    428 
    429             /* The playback will end when EOF is reached */
    430             while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) {
    431                 printf("index = %d\n", data.frameIndex ); fflush(stdout);
    432                 Pa_Sleep(1000);
    433             }
    434             if( err < 0 ) goto done;
    435         }
    436         
    437         err = Pa_CloseStream( stream );
    438         if( err != paNoError ) goto done;
    439 
    440         fclose(data.file);
    441         
    442         printf("Done.\n"); fflush(stdout);
    443     }
    444 
    445 done:
    446     Pa_Terminate();
    447     if( data.ringBufferData )       /* Sure it is NULL or valid. */
    448         PaUtil_FreeMemory( data.ringBufferData );
    449     if( err != paNoError )
    450     {
    451         fprintf( stderr, "An error occured while using the portaudio stream\n" );
    452         fprintf( stderr, "Error number: %d\n", err );
    453         fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
    454         err = 1;          /* Always return 0 or 1, but no other return codes. */
    455     }
    456     return err;
    457 }
    458