libcm/cmSyncRecd.c

565 lines
14 KiB
C
Raw Normal View History

#include "cmGlobal.h"
#include "cmFloatTypes.h"
#include "cmRpt.h"
#include "cmErr.h"
#include "cmCtx.h"
#include "cmMem.h"
#include "cmMallocDebug.h"
#include "cmTime.h"
#include "cmFile.h"
#include "cmAudioFile.h"
#include "cmSyncRecd.h"
2013-12-17 13:54:07 +00:00
#include "cmVectOpsTemplateMain.h"
#include "cmMidi.h"
typedef enum
{
kInvalidSrId,
kMidiSrId,
kAudioSrId
} cmSrTypeId_t;
typedef struct cmSrMidi_str
{
cmTimeSpec_t timestamp;
unsigned status;
unsigned d0;
unsigned d1;
} cmSrMidi_t;
typedef struct cmSrAudio_str
{
cmTimeSpec_t timestamp;
unsigned smpIdx;
} cmSrAudio_t;
typedef struct cmSrRecd_str
{
cmSrTypeId_t tid;
union
{
cmSrMidi_t m;
cmSrAudio_t a;
} u;
} cmSrRecd_t;
enum
{
kReadSrFl = 0x01, // This is a read (not a write) file
kFileUUSrId = 0xf00d
};
typedef struct cmSr_str
{
2013-12-17 13:54:07 +00:00
cmErr_t err;
cmFileH_t fH;
cmAudioFileH_t afH;
unsigned flags; // See kXXXSrFl
cmSrRecd_t* cache; // cache[cn]
cmSrAudio_t* map; //
unsigned cn; // count of records in cache[].
unsigned ci; // next cache recd index
unsigned fn; // count of recds written to file
long offs;
cmAudioFileInfo_t afInfo;
} cmSr_t;
cmSyncRecdH_t cmSyncRecdNullHandle = cmSTATIC_NULL_HANDLE;
cmSr_t* _cmSrHtoP( cmSyncRecdH_t h )
{
cmSr_t* p = (cmSr_t*)h.h;
assert(p!=NULL);
return p;
}
cmSyRC_t _cmSrWriteCache( cmSr_t* p )
{
if( cmFileWrite(p->fH,p->cache,p->ci * sizeof(cmSrRecd_t)) != kOkFileRC )
return cmErrMsg(&p->err,kFileFailSyRC,"File write failed.");
p->fn += p->ci;
p->ci = 0;
return kOkSyRC;
}
cmSyRC_t _cmSrFinal( cmSr_t* p )
{
cmSyRC_t rc = kOkSyRC;
// write any remaining cache records
if( cmIsFlag(p->flags,kReadSrFl) == false )
{
if((rc = _cmSrWriteCache(p)) == kOkSyRC )
{
if(cmFileSeek(p->fH,kBeginFileFl,p->offs) != kOkFileRC )
rc = cmErrMsg(&p->err,kFileFailSyRC, "File seek fail on file offset positioning.");
else
if(cmFileWriteUInt(p->fH,&p->fn,1) != kOkFileRC )
rc = cmErrMsg(&p->err,kFileFailSyRC, "File write failed on record count.");
}
}
// release the audio file object
if( cmAudioFileIsValid(p->afH) )
if( cmAudioFileDelete(&p->afH) != kOkAfRC )
return cmErrMsg(&p->err,kAudioFileFailSyRC,"Audio file object delete failed.");
// release the sync-recd file object
if( cmFileIsValid(p->fH) )
if( cmFileClose(&p->fH) != kOkFileRC )
return cmErrMsg(&p->err,kFileFailSyRC,"File close failed.");
cmMemFree(p->cache);
cmMemFree(p->map);
cmMemFree(p);
return rc;
}
cmSr_t* _cmSrAlloc( cmCtx_t* ctx, unsigned flags )
{
cmSr_t* p = cmMemAllocZ(cmSr_t,1);
cmErrSetup(&p->err,&ctx->rpt,"SyncRecd");
p->cn = 2048;
p->cache = cmMemAllocZ(cmSrRecd_t,p->cn);
return p;
}
cmSyRC_t cmSyncRecdCreate( cmCtx_t* ctx, cmSyncRecdH_t* hp, const cmChar_t* srFn, const cmChar_t* audioFn, double srate, unsigned chCnt, unsigned bits )
{
cmSyRC_t rc = kOkSyRC;
cmRC_t afRC = kOkAfRC;
assert( audioFn != NULL );
if((rc = cmSyncRecdFinal(hp)) != kOkSyRC )
return rc;
cmSr_t* p = _cmSrAlloc(ctx,0);
if( cmFileOpen(&p->fH,srFn,kWriteFileFl,&ctx->rpt) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to create the sync-recd file '%s'.",cmStringNullGuard(srFn));
goto errLabel;
}
if( cmAudioFileIsValid(p->afH = cmAudioFileNewCreate(audioFn,srate,bits,chCnt,&afRC,&ctx->rpt))==false)
{
rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Unable to create the sync-recd audio file '%s'.",cmStringNullGuard(audioFn));
goto errLabel;
}
unsigned fileUUId = kFileUUSrId;
unsigned audioFnCnt = strlen(audioFn)+1;
if( cmFileWriteUInt(p->fH,&fileUUId,1) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"File write failed on UUID.");
goto errLabel;
}
if( cmFileWriteUInt(p->fH,&audioFnCnt,1) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"File write failed on audio file length write count.");
goto errLabel;
}
if( cmFileWriteChar(p->fH,audioFn,audioFnCnt) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"File write failed on audio file string.");
goto errLabel;
}
if( cmFileTell(p->fH,&p->offs) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to determine file offset.");
goto errLabel;
}
if( cmFileWriteUInt(p->fH,&p->fn,1) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"File write failed on initial record count.");
goto errLabel;
}
hp->h = p;
errLabel:
if( rc != kOkSyRC )
_cmSrFinal(p);
return rc;
}
cmSyRC_t cmSyncRecdOpen( cmCtx_t* ctx, cmSyncRecdH_t* hp, const cmChar_t* srFn )
{
cmSyRC_t rc = kOkSyRC;
cmRC_t afRC = kOkAfRC;
unsigned fileUUId = cmInvalidId;
unsigned audioFnCnt = 0;
cmChar_t* audioFn = NULL;
unsigned i;
unsigned acnt = 0;
unsigned mcnt = 0;
unsigned* tiV = NULL;
if((rc = cmSyncRecdFinal(hp)) != kOkSyRC )
return rc;
cmSr_t* p = _cmSrAlloc(ctx,kReadSrFl);
if( cmFileOpen(&p->fH,srFn,kReadFileFl,&ctx->rpt) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to open the sync-recd file '%s'.",cmStringNullGuard(srFn));
goto errLabel;
}
if( cmFileReadUInt(p->fH,&fileUUId,1) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"File read failed on UUId.");
goto errLabel;
}
if( cmFileReadUInt(p->fH,&audioFnCnt,1) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"File read failed on audio file name count.");
goto errLabel;
}
audioFn = cmMemAllocZ(cmChar_t,audioFnCnt);
if( cmFileReadChar(p->fH,audioFn,audioFnCnt) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"File read failed on audio file string.");
goto errLabel;
}
if( cmFileReadUInt(p->fH,&p->fn,1) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"File read failed on record count.");
goto errLabel;
}
// store the file offset to the first recd
if( cmFileTell(p->fH,&p->offs) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to determine the current file offset.");
goto errLabel;
}
// read each file - and count the types
for(i=0; i<p->fn; ++i)
{
cmSrRecd_t r;
if( cmFileRead(p->fH,&r,sizeof(r)) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to read the record at index %i.");
goto errLabel;
}
switch(r.tid)
{
case kMidiSrId:
mcnt += 1;
break;
case kAudioSrId:
acnt += 1;
break;
default:
{ assert(0); }
}
}
2013-12-17 13:54:07 +00:00
printf("%i %i = %i\n",mcnt,acnt,p->fn);
// rewind to the begining of the records
if( cmFileSeek(p->fH,kBeginFileFl,p->offs) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to seek to first recd offset.");
goto errLabel;
}
// allocate space to hold the MIDI records
p->cn = mcnt;
p->ci = 0;
p->cache = cmMemResizeZ(cmSrRecd_t,p->cache,p->cn);
p->map = cmMemAllocZ(cmSrAudio_t,p->cn);
tiV = cmMemAllocZ(unsigned,p->cn);
for(i=0; p->ci<p->cn && i<p->fn; ++i)
{
if( cmFileRead(p->fH,p->cache + p->ci,sizeof(cmSrRecd_t)) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to read the record at index %i.");
goto errLabel;
}
if( p->cache[p->ci].tid == kMidiSrId )
p->ci += 1;
}
// assert that all the MIDI records were read
assert( p->ci == p->cn);
// rewind to the first recd
if( cmFileSeek(p->fH,kBeginFileFl,p->offs) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to seek to first recd offset.");
goto errLabel;
}
// for each recd in the file
for(i=0; i<p->fn; ++i)
{
cmSrRecd_t r;
if( cmFileRead(p->fH,&r,sizeof(r)) != kOkFileRC )
{
rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to read the record at index %i.");
goto errLabel;
}
// if this is an audio record
if( r.tid == kAudioSrId )
{
unsigned j;
// for each midi record
for(j=0; j<p->cn; ++j)
{
// measure the time interval between this midi and this audio recd
unsigned time_interval_micros = cmTimeAbsElapsedMicros(&r.u.a.timestamp,&p->cache[j].u.m.timestamp);
// if the audio recd is closer to this midi recd than prior audio records ...
2013-12-17 13:54:07 +00:00
if( time_interval_micros < tiV[j] || i==0 )
{
// ... then store the audio time stamp in the map
2013-12-17 13:54:07 +00:00
tiV[j] = time_interval_micros;
p->map[j].timestamp = r.u.a.timestamp;
p->map[j].smpIdx = r.u.a.smpIdx;
}
}
}
}
// open the audio file
2013-12-17 13:54:07 +00:00
if( cmAudioFileIsValid(p->afH = cmAudioFileNewOpen(audioFn,&p->afInfo,&afRC,&ctx->rpt ))==false)
{
rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Unable to open the sync-recd audio file '%s'.",cmStringNullGuard(audioFn));
goto errLabel;
}
p->flags = cmSetFlag(p->flags,kReadSrFl);
hp->h = p;
errLabel:
cmMemFree(tiV);
cmMemFree(audioFn);
if( rc != kOkSyRC )
_cmSrFinal(p);
return rc;
}
cmSyRC_t cmSyncRecdFinal( cmSyncRecdH_t* hp )
{
cmSyRC_t rc = kOkSyRC;
if( hp==NULL || cmSyncRecdIsValid(*hp)==false)
return rc;
cmSr_t* p = _cmSrHtoP(*hp);
if((rc = _cmSrFinal(p)) != kOkSyRC )
return rc;
hp->h = NULL;
return rc;
}
bool cmSyncRecdIsValid( cmSyncRecdH_t h )
{ return h.h != NULL; }
cmSyRC_t cmSyncRecdMidiWrite( cmSyncRecdH_t h, const cmTimeSpec_t* timestamp, unsigned status, unsigned d0, unsigned d1 )
{
cmSyRC_t rc = kOkSyRC;
cmSr_t* p = _cmSrHtoP(h);
cmSrRecd_t* rp = p->cache + p->ci;
rp->tid = kMidiSrId;
rp->u.m.timestamp = *timestamp;
rp->u.m.status = status;
rp->u.m.d0 = d0;
rp->u.m.d1 = d1;
p->ci += 1;
if( p->ci == p->cn )
rc = _cmSrWriteCache(p);
return rc;
}
cmSyRC_t cmSyncRecdAudioWrite( cmSyncRecdH_t h, const cmTimeSpec_t* timestamp, unsigned smpIdx, const cmSample_t* ch[], unsigned chCnt, unsigned frmCnt )
{
cmSyRC_t rc = kOkSyRC;
cmSr_t* p = _cmSrHtoP(h);
cmSrRecd_t* rp = p->cache + p->ci;
rp->tid = kAudioSrId;
rp->u.a.timestamp = *timestamp;
rp->u.a.smpIdx = smpIdx;
p->ci += 1;
if( p->ci == p->cn )
if((rc = _cmSrWriteCache(p)) != kOkSyRC )
goto errLabel;
if( cmAudioFileWriteSample(p->afH,frmCnt,chCnt,(cmSample_t**)ch) != kOkAfRC )
rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Audio file write failed.");
errLabel:
return rc;
}
cmSyRC_t cmSyncRecdPrint( cmSyncRecdH_t h )
{
cmSyRC_t rc = kOkSyRC;
2013-12-17 13:54:07 +00:00
cmSr_t* p = _cmSrHtoP(h);
unsigned i;
2013-12-17 13:54:07 +00:00
if( cmIsFlag(p->flags,kReadSrFl)==false)
return cmErrMsg(&p->err,kInvalidOpSyRC,"The 'print' operation is only valid on sync-recd files opened for reading.");
for(i=0; i<p->cn; ++i)
{
cmSrRecd_t* r = p->cache + i;
2013-12-17 13:54:07 +00:00
cmRptPrintf(p->err.rpt,"0x%x %3i %3i %ld %5.3f : %ld %5.3f %i",r->u.m.status,r->u.m.d0,r->u.m.d1,r->u.m.timestamp.tv_sec,r->u.m.timestamp.tv_nsec/1000000000.0,p->map[i].timestamp.tv_sec,p->map[i].timestamp.tv_nsec/1000000000.0,p->map[i].smpIdx);
}
return rc;
}
cmSyRC_t cmSyncRecdAudioFile( cmSyncRecdH_t h, const cmChar_t* fn )
2013-12-17 13:54:07 +00:00
{
cmSyRC_t rc = kOkSyRC;
2013-12-17 13:54:07 +00:00
cmSr_t* p = _cmSrHtoP(h);
cmAudioFileH_t afH = cmNullAudioFileH;
unsigned chCnt = 2;
unsigned frmCnt = 1024;
unsigned chIdx = 0;
cmRC_t afRC = kOkAfRC;
unsigned actFrmCnt = 0;
unsigned smpIdx = 0;
cmSample_t* chs[chCnt];
cmSample_t buf[frmCnt*2];
chs[0] = buf;
chs[1] = buf+frmCnt;
if( cmIsFlag(p->flags,kReadSrFl)==false)
return cmErrMsg(&p->err,kInvalidOpSyRC,"The 'audio-file-output' operation is only valid on sync-recd files opened for reading.");
2013-12-17 13:54:07 +00:00
/// Open an audio file for writing
if(cmAudioFileIsValid(afH = cmAudioFileNewCreate(fn, p->afInfo.srate, p->afInfo.bits, chCnt, &afRC, p->err.rpt))==false)
{
rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Unable to create the synchronized audio file '%s'.",cmStringNullGuard(fn));
2013-12-17 13:54:07 +00:00
goto errLabel;
}
// rewind the input audio file
if( cmAudioFileSeek(p->afH,0) != kOkAfRC )
{
rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Seek failed during synchronized audio file output.");
2013-12-17 13:54:07 +00:00
goto errLabel;
}
2013-12-17 13:54:07 +00:00
actFrmCnt = frmCnt;
// for each buffer of audio
for(smpIdx=0; actFrmCnt==frmCnt; smpIdx+=actFrmCnt)
{
unsigned i;
// read frmCnt samples from the first channel of the input audio file
if( cmAudioFileReadSample(p->afH, frmCnt, chIdx, 1, chs, &actFrmCnt ) != kOkAfRC )
{
rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Audio file read failed.");
2013-12-17 13:54:07 +00:00
break;
}
// zero the output buffer for the second audio channel
cmVOS_Zero(chs[1],frmCnt);
// insert impulses at the location of the MIDI messages.
for(i=0; i<p->cn; ++i)
if( p->cache[i].u.m.status==kNoteOnMdId && smpIdx <= p->map[i].smpIdx && p->map[i].smpIdx < smpIdx+frmCnt )
chs[1][ p->map[i].smpIdx - smpIdx ] = 1.0;
// write the audio output samples
if( cmAudioFileWriteSample(afH, frmCnt, chCnt, chs ) != kOkAfRC )
{
rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Synchronized audio file write failed.");
2013-12-17 13:54:07 +00:00
break;
}
}
errLabel:
if( cmAudioFileDelete(&afH) != kOkAfRC )
rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Synchronized audio file close failed.");
2013-12-17 13:54:07 +00:00
return rc;
}
cmSyRC_t cmSyncRecdTest( cmCtx_t* ctx )
{
enum
{
kOkTestRC,
kTestFailRC,
};
cmSyRC_t rc = kOkSyRC;
2013-12-17 13:54:07 +00:00
const cmChar_t* srFn = "/home/kevin/temp/kr/sr/sr0.sr";
const cmChar_t* aFn = "/home/kevin/temp/kr/sr/sync_af.aiff";
cmErr_t err;
cmSyncRecdH_t srH = cmSyncRecdNullHandle;
cmErrSetup(&err,&ctx->rpt,"SyncRecdTest");
if((rc = cmSyncRecdOpen(ctx, &srH, srFn )) != kOkSyRC )
{
cmErrMsg(&err,kTestFailRC,"Sync-recd open failed.");
goto errLabel;
}
cmSyncRecdPrint(srH);
2013-12-17 13:54:07 +00:00
cmSyncRecdAudioFile(srH,aFn);
errLabel:
if((rc = cmSyncRecdFinal(&srH)) != kOkSyRC )
cmErrMsg(&err,kTestFailRC,"Sync-recd close failed.");
return rc;
}