cwIoPresetSelApp.cpp,cwIo.h/cpp,cwMidiPort.h,cwMidiAlsa.cpp :
Added audio and MIDI latency reporting implementation.
This commit is contained in:
parent
42882c2e3d
commit
57f3d06f37
115
cwIo.cpp
115
cwIo.cpp
@ -31,6 +31,7 @@
|
|||||||
#include "cwWebSock.h"
|
#include "cwWebSock.h"
|
||||||
#include "cwUi.h"
|
#include "cwUi.h"
|
||||||
|
|
||||||
|
#include "cwVectOps.h"
|
||||||
|
|
||||||
namespace cw
|
namespace cw
|
||||||
{
|
{
|
||||||
@ -164,7 +165,11 @@ namespace cw
|
|||||||
ui::appIdMap_t* uiMapA; // Application supplied id's for the UI resource supplied with the cfg script via 'uiCfgFn'.
|
ui::appIdMap_t* uiMapA; // Application supplied id's for the UI resource supplied with the cfg script via 'uiCfgFn'.
|
||||||
unsigned uiMapN; //
|
unsigned uiMapN; //
|
||||||
bool uiAsyncFl;
|
bool uiAsyncFl;
|
||||||
|
|
||||||
|
sample_t latency_meas_thresh_db;
|
||||||
|
sample_t latency_meas_thresh_lin;
|
||||||
|
bool latency_meas_enable_fl;
|
||||||
|
latency_meas_result_t latency_meas_result;
|
||||||
} io_t;
|
} io_t;
|
||||||
|
|
||||||
|
|
||||||
@ -1070,7 +1075,7 @@ namespace cw
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get sample ponters or advance the pointers on the buffer associates with each device.
|
// Get sample pointers or advance the pointers on the buffer associates with each device.
|
||||||
enum { kAudioGroupGetBuf, kAudioGroupAdvBuf };
|
enum { kAudioGroupGetBuf, kAudioGroupAdvBuf };
|
||||||
void _audioGroupProcSampleBufs( io_t* p, audioGroup_t* ag, unsigned processTypeId, unsigned inputFl )
|
void _audioGroupProcSampleBufs( io_t* p, audioGroup_t* ag, unsigned processTypeId, unsigned inputFl )
|
||||||
{
|
{
|
||||||
@ -1100,6 +1105,39 @@ namespace cw
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _audio_latency_measure( io_t* p, const audio_msg_t& msg )
|
||||||
|
{
|
||||||
|
if( p->latency_meas_enable_fl )
|
||||||
|
{
|
||||||
|
|
||||||
|
if( msg.iBufChCnt && p->latency_meas_result.audio_in_ts.tv_nsec == 0 )
|
||||||
|
{
|
||||||
|
sample_t rms = vop::rms(msg.iBufArray[0], msg.dspFrameCnt );
|
||||||
|
|
||||||
|
if( rms > p->latency_meas_thresh_lin )
|
||||||
|
time::get(p->latency_meas_result.audio_in_ts);
|
||||||
|
|
||||||
|
if( rms > p->latency_meas_result.audio_in_rms_max )
|
||||||
|
p->latency_meas_result.audio_in_rms_max = rms;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( msg.oBufChCnt && p->latency_meas_result.audio_out_ts.tv_nsec == 0 )
|
||||||
|
{
|
||||||
|
sample_t rms = vop::rms(msg.oBufArray[0], msg.dspFrameCnt );
|
||||||
|
|
||||||
|
if( rms > p->latency_meas_thresh_lin )
|
||||||
|
time::get(p->latency_meas_result.audio_out_ts);
|
||||||
|
|
||||||
|
if( rms > p->latency_meas_result.audio_out_rms_max )
|
||||||
|
p->latency_meas_result.audio_out_rms_max = rms;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->latency_meas_enable_fl = p->latency_meas_result.audio_in_ts.tv_nsec == 0 ||
|
||||||
|
p->latency_meas_result.audio_out_ts.tv_nsec == 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This is the audio processing thread function. Block on the audio group condition var
|
// This is the audio processing thread function. Block on the audio group condition var
|
||||||
// which is triggered when all the devices in the group are ready by _audioGroupNotifyIfReady().
|
// which is triggered when all the devices in the group are ready by _audioGroupNotifyIfReady().
|
||||||
bool _audioGroupThreadFunc( void* arg )
|
bool _audioGroupThreadFunc( void* arg )
|
||||||
@ -1134,6 +1172,8 @@ namespace cw
|
|||||||
if((rc = _ioCallback( ag->p, ag->asyncFl, &msg)) != kOkRC )
|
if((rc = _ioCallback( ag->p, ag->asyncFl, &msg)) != kOkRC )
|
||||||
cwLogError(rc,"Audio app callback failed %i.",ag->asyncFl);
|
cwLogError(rc,"Audio app callback failed %i.",ag->asyncFl);
|
||||||
|
|
||||||
|
_audio_latency_measure( ag->p, ag->msg );
|
||||||
|
|
||||||
_audioGroupProcSampleBufs( ag->p, ag, kAudioGroupAdvBuf, true );
|
_audioGroupProcSampleBufs( ag->p, ag, kAudioGroupAdvBuf, true );
|
||||||
_audioGroupProcSampleBufs( ag->p, ag, kAudioGroupAdvBuf, false );
|
_audioGroupProcSampleBufs( ag->p, ag, kAudioGroupAdvBuf, false );
|
||||||
}
|
}
|
||||||
@ -3753,6 +3793,77 @@ cw::rc_t cw::io::uiSendMsg( handle_t h, const char* msg )
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cw::io::latency_measure_setup(handle_t h)
|
||||||
|
{
|
||||||
|
io_t* p = _handleToPtr(h);
|
||||||
|
p->latency_meas_enable_fl = true;
|
||||||
|
p->latency_meas_thresh_db = -50;
|
||||||
|
p->latency_meas_thresh_lin = pow(10.0,p->latency_meas_thresh_db/20.0);
|
||||||
|
p->latency_meas_result.note_on_input_ts = {0};
|
||||||
|
p->latency_meas_result.note_on_output_ts = {0};
|
||||||
|
p->latency_meas_result.audio_in_ts = {0};
|
||||||
|
p->latency_meas_result.audio_out_ts = {0};
|
||||||
|
p->latency_meas_result.audio_in_rms_max = 0;
|
||||||
|
p->latency_meas_result.audio_out_rms_max = 0;
|
||||||
|
|
||||||
|
if( p->midiH.isValid() )
|
||||||
|
latency_measure_setup(p->midiH);
|
||||||
|
}
|
||||||
|
|
||||||
|
cw::io::latency_meas_result_t cw::io::latency_measure_result(handle_t h)
|
||||||
|
{
|
||||||
|
io_t* p = _handleToPtr(h);
|
||||||
|
|
||||||
|
if( p->midiH.isValid() )
|
||||||
|
{
|
||||||
|
midi::device::latency_meas_result_t r = latency_measure_result(p->midiH);
|
||||||
|
|
||||||
|
p->latency_meas_result.note_on_input_ts = r.note_on_input_ts;
|
||||||
|
p->latency_meas_result.note_on_output_ts = r.note_on_output_ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
return p->latency_meas_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cw::io::latency_measure_report(handle_t h)
|
||||||
|
{
|
||||||
|
io_t* p = _handleToPtr(h);
|
||||||
|
latency_meas_result_t r = latency_measure_result(h);
|
||||||
|
unsigned t0,t1;
|
||||||
|
|
||||||
|
if( r.note_on_input_ts.tv_nsec )
|
||||||
|
{
|
||||||
|
if( r.note_on_output_ts.tv_nsec )
|
||||||
|
{
|
||||||
|
t0 = time::elapsedMicros(r.note_on_input_ts,r.note_on_output_ts);
|
||||||
|
printf("midi in - midi out: %6i\n",t0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if( r.audio_in_ts.tv_nsec )
|
||||||
|
{
|
||||||
|
t0 = time::elapsedMicros(r.note_on_output_ts,r.audio_in_ts);
|
||||||
|
t1 = time::elapsedMicros(r.note_on_input_ts,r.audio_in_ts);
|
||||||
|
printf("midi out - audio in: %6i %6i\n",t0,t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if( r.audio_out_ts.tv_nsec )
|
||||||
|
{
|
||||||
|
t0 = time::elapsedMicros(r.audio_in_ts,r.audio_out_ts);
|
||||||
|
t1 = time::elapsedMicros(r.note_on_input_ts,r.audio_out_ts);
|
||||||
|
printf("audio in - audio out: %6i %6i\n",t0,t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Thresh: %f %f db\n",p->latency_meas_thresh_db,p->latency_meas_thresh_lin);
|
||||||
|
if( r.audio_in_rms_max != 0 )
|
||||||
|
printf("audio in max:%f %f dB\n", r.audio_in_rms_max, 20.0*log10(r.audio_in_rms_max));
|
||||||
|
|
||||||
|
if( r.audio_out_rms_max != 0 )
|
||||||
|
printf("audio out max:%f %f dB\n", r.audio_out_rms_max, 20.0*log10(r.audio_out_rms_max));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void cw::io::uiReport( handle_t h )
|
void cw::io::uiReport( handle_t h )
|
||||||
{
|
{
|
||||||
|
15
cwIo.h
15
cwIo.h
@ -406,6 +406,21 @@ namespace cw
|
|||||||
|
|
||||||
rc_t uiSendMsg( handle_t h, const char* msg );
|
rc_t uiSendMsg( handle_t h, const char* msg );
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
time::spec_t note_on_input_ts;
|
||||||
|
time::spec_t note_on_output_ts;
|
||||||
|
time::spec_t audio_in_ts;
|
||||||
|
time::spec_t audio_out_ts;
|
||||||
|
sample_t audio_in_rms_max;
|
||||||
|
sample_t audio_out_rms_max;
|
||||||
|
} latency_meas_result_t;
|
||||||
|
|
||||||
|
void latency_measure_setup(handle_t h);
|
||||||
|
latency_meas_result_t latency_measure_result(handle_t h);
|
||||||
|
void latency_measure_report(handle_t h);
|
||||||
|
|
||||||
void uiRealTimeReport( handle_t h );
|
void uiRealTimeReport( handle_t h );
|
||||||
void uiReport( handle_t h );
|
void uiReport( handle_t h );
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ namespace cw
|
|||||||
kIoReportBtnId,
|
kIoReportBtnId,
|
||||||
kNetPrintBtnId,
|
kNetPrintBtnId,
|
||||||
kReportBtnId,
|
kReportBtnId,
|
||||||
|
kLatencyBtnId,
|
||||||
|
|
||||||
kStartBtnId,
|
kStartBtnId,
|
||||||
kStopBtnId,
|
kStopBtnId,
|
||||||
@ -148,6 +149,7 @@ namespace cw
|
|||||||
{ kPanelDivId, kIoReportBtnId, "ioReportBtnId" },
|
{ kPanelDivId, kIoReportBtnId, "ioReportBtnId" },
|
||||||
{ kPanelDivId, kNetPrintBtnId, "netPrintBtnId" },
|
{ kPanelDivId, kNetPrintBtnId, "netPrintBtnId" },
|
||||||
{ kPanelDivId, kReportBtnId, "reportBtnId" },
|
{ kPanelDivId, kReportBtnId, "reportBtnId" },
|
||||||
|
{ kPanelDivId, kLatencyBtnId, "latencyBtnId" },
|
||||||
|
|
||||||
{ kPanelDivId, kStartBtnId, "startBtnId" },
|
{ kPanelDivId, kStartBtnId, "startBtnId" },
|
||||||
{ kPanelDivId, kStopBtnId, "stopBtnId" },
|
{ kPanelDivId, kStopBtnId, "stopBtnId" },
|
||||||
@ -1117,10 +1119,10 @@ namespace cw
|
|||||||
rc_t rc = kOkRC;
|
rc_t rc = kOkRC;
|
||||||
|
|
||||||
if( msg.u.midi != nullptr )
|
if( msg.u.midi != nullptr )
|
||||||
{
|
{
|
||||||
|
const io::midi_msg_t& m = *msg.u.midi;
|
||||||
const io::midi_msg_t& m = *msg.u.midi;
|
|
||||||
const midi::packet_t* pkt = m.pkt;
|
const midi::packet_t* pkt = m.pkt;
|
||||||
|
|
||||||
// for each midi msg
|
// for each midi msg
|
||||||
for(unsigned j = 0; j<pkt->msgCnt; ++j)
|
for(unsigned j = 0; j<pkt->msgCnt; ++j)
|
||||||
{
|
{
|
||||||
@ -1131,8 +1133,8 @@ namespace cw
|
|||||||
}
|
}
|
||||||
else // this is a triple
|
else // this is a triple
|
||||||
{
|
{
|
||||||
midi::msg_t* mm = pkt->msgArray + j;
|
|
||||||
time::spec_t timestamp;
|
time::spec_t timestamp;
|
||||||
|
midi::msg_t* mm = pkt->msgArray + j;
|
||||||
unsigned id = app->enableRecordFl ? last_store_index(app->mrpH) : kInvalidId;
|
unsigned id = app->enableRecordFl ? last_store_index(app->mrpH) : kInvalidId;
|
||||||
unsigned loc = app->beg_play_loc;
|
unsigned loc = app->beg_play_loc;
|
||||||
|
|
||||||
@ -1346,9 +1348,6 @@ namespace cw
|
|||||||
evt_cnt = midi_record_play::event_count(app->mrpH);
|
evt_cnt = midi_record_play::event_count(app->mrpH);
|
||||||
|
|
||||||
io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), evt_cnt );
|
io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), evt_cnt );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errLabel:
|
errLabel:
|
||||||
@ -2986,6 +2985,11 @@ namespace cw
|
|||||||
preset_sel::report_presets(app->psH);
|
preset_sel::report_presets(app->psH);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case kLatencyBtnId:
|
||||||
|
latency_measure_report(app->ioH);
|
||||||
|
latency_measure_setup(app->ioH);
|
||||||
|
break;
|
||||||
|
|
||||||
case kSaveBtnId:
|
case kSaveBtnId:
|
||||||
_on_ui_save(app);
|
_on_ui_save(app);
|
||||||
break;
|
break;
|
||||||
|
@ -55,6 +55,11 @@ namespace cw
|
|||||||
unsigned prvTimeMicroSecs; // time of last recognized event in microseconds
|
unsigned prvTimeMicroSecs; // time of last recognized event in microseconds
|
||||||
unsigned eventCnt; // count of recognized events
|
unsigned eventCnt; // count of recognized events
|
||||||
time::spec_t baseTimeStamp;
|
time::spec_t baseTimeStamp;
|
||||||
|
|
||||||
|
bool latency_meas_enable_in_fl;
|
||||||
|
bool latency_meas_enable_out_fl;
|
||||||
|
latency_meas_result_t latency_meas_result;
|
||||||
|
|
||||||
} device_t;
|
} device_t;
|
||||||
|
|
||||||
#define _cmMpErrMsg( rc, alsaRc, str ) cwLogError(kOpFailRC,"%s : ALSA Error:%i %s",(str),(alsaRc),snd_strerror(alsaRc))
|
#define _cmMpErrMsg( rc, alsaRc, str ) cwLogError(kOpFailRC,"%s : ALSA Error:%i %s",(str),(alsaRc),snd_strerror(alsaRc))
|
||||||
@ -104,34 +109,33 @@ namespace cw
|
|||||||
*d1 = v & 0x7f;
|
*d1 = v & 0x7f;
|
||||||
}
|
}
|
||||||
|
|
||||||
rc_t _cmMpPoll(device_t* p)
|
rc_t _handle_alsa_device( device_t* p )
|
||||||
{
|
{
|
||||||
rc_t rc = kOkRC;
|
rc_t rc = kOkRC;
|
||||||
int timeOutMs = 50;
|
|
||||||
|
|
||||||
snd_seq_event_t *ev;
|
snd_seq_event_t *ev;
|
||||||
|
|
||||||
|
|
||||||
if (poll(p->alsa_fd, p->alsa_fdCnt, timeOutMs) > 0)
|
do
|
||||||
{
|
{
|
||||||
int rc = 1;
|
int alsa_rc = snd_seq_event_input(p->h,&ev);
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
rc = snd_seq_event_input(p->h,&ev);
|
|
||||||
|
|
||||||
// if no input
|
// if no input
|
||||||
if( rc == -EAGAIN )
|
if( alsa_rc == -EAGAIN )
|
||||||
{
|
{
|
||||||
// TODO: report or at least count error
|
// TODO: report or at least count error
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if input buffer overrun
|
// if input buffer overrun
|
||||||
if( rc == -ENOSPC )
|
if( alsa_rc == -ENOSPC )
|
||||||
{
|
{
|
||||||
// TODO: report or at least count error
|
// TODO: report or at least count error
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Improve performance of _cmMpClientIdToDev() and _cmMpInPortIdToPort().
|
||||||
|
// They currently use linear searchs!
|
||||||
|
|
||||||
// get the device this event arrived from
|
// get the device this event arrived from
|
||||||
if( p->prvRcvDev==NULL || p->prvRcvDev->clientId != ev->source.client )
|
if( p->prvRcvDev==NULL || p->prvRcvDev->clientId != ev->source.client )
|
||||||
@ -163,6 +167,15 @@ namespace cw
|
|||||||
status = kNoteOnMdId;
|
status = kNoteOnMdId;
|
||||||
d0 = ev->data.note.note;
|
d0 = ev->data.note.note;
|
||||||
d1 = ev->data.note.velocity;
|
d1 = ev->data.note.velocity;
|
||||||
|
|
||||||
|
if( p->latency_meas_enable_in_fl )
|
||||||
|
{
|
||||||
|
p->latency_meas_enable_in_fl = false;
|
||||||
|
time::get(p->latency_meas_result.note_on_input_ts);
|
||||||
|
//p->latency_meas_result.note_on_input_ts.tv_sec = ev->time.time.tv_sec;
|
||||||
|
//p->latency_meas_result.note_on_input_ts.tv_nsec = ev->time.time.tv_nsec;
|
||||||
|
}
|
||||||
|
|
||||||
//printf("%s (%i : %i) (%i)\n", snd_seq_ev_is_abstime(ev)?"abs":"rel",ev->time.time.tv_sec,ev->time.time.tv_nsec, deltaMicroSecs/1000);
|
//printf("%s (%i : %i) (%i)\n", snd_seq_ev_is_abstime(ev)?"abs":"rel",ev->time.time.tv_sec,ev->time.time.tv_nsec, deltaMicroSecs/1000);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -263,6 +276,18 @@ namespace cw
|
|||||||
}while( snd_seq_event_input_pending(p->h,0));
|
}while( snd_seq_event_input_pending(p->h,0));
|
||||||
|
|
||||||
parser::transmit(p->prvRcvPort->parserH);
|
parser::transmit(p->prvRcvPort->parserH);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t _cmMpPoll(device_t* p)
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
int timeOutMs = 50;
|
||||||
|
|
||||||
|
if (poll(p->alsa_fd, p->alsa_fdCnt, timeOutMs) > 0)
|
||||||
|
{
|
||||||
|
rc = _handle_alsa_device(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
@ -739,6 +764,12 @@ cw::rc_t cw::midi::device::send( handle_t h, unsigned devIdx, unsigned portIdx,
|
|||||||
ev.type = SND_SEQ_EVENT_NOTEON;
|
ev.type = SND_SEQ_EVENT_NOTEON;
|
||||||
ev.data.note.note = d0;
|
ev.data.note.note = d0;
|
||||||
ev.data.note.velocity = d1;
|
ev.data.note.velocity = d1;
|
||||||
|
|
||||||
|
if( p->latency_meas_enable_out_fl )
|
||||||
|
{
|
||||||
|
p->latency_meas_enable_out_fl = false;
|
||||||
|
time::get(p->latency_meas_result.note_on_output_ts);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case kPolyPresMdId:
|
case kPolyPresMdId:
|
||||||
@ -876,6 +907,21 @@ bool cw::midi::device::usesCallback( handle_t h, unsigned devIdx, unsi
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cw::midi::device::latency_measure_setup(handle_t h)
|
||||||
|
{
|
||||||
|
device_t* p = _handleToPtr(h);
|
||||||
|
|
||||||
|
p->latency_meas_result.note_on_input_ts = {0};
|
||||||
|
p->latency_meas_result.note_on_output_ts = {0};
|
||||||
|
p->latency_meas_enable_in_fl = true;
|
||||||
|
p->latency_meas_enable_out_fl = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
cw::midi::device::latency_meas_result_t cw::midi::device::latency_measure_result(handle_t h)
|
||||||
|
{
|
||||||
|
device_t* p = _handleToPtr(h);
|
||||||
|
return p->latency_meas_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void cw::midi::device::report( handle_t h, textBuf::handle_t tbH )
|
void cw::midi::device::report( handle_t h, textBuf::handle_t tbH )
|
||||||
|
10
cwMidiPort.h
10
cwMidiPort.h
@ -89,6 +89,16 @@ namespace cw
|
|||||||
rc_t installCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr );
|
rc_t installCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr );
|
||||||
rc_t removeCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr );
|
rc_t removeCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr );
|
||||||
bool usesCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr );
|
bool usesCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr );
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
time::spec_t note_on_input_ts;
|
||||||
|
time::spec_t note_on_output_ts;
|
||||||
|
} latency_meas_result_t;
|
||||||
|
|
||||||
|
// Reset the latency measurement process.
|
||||||
|
void latency_measure_setup(handle_t h);
|
||||||
|
latency_meas_result_t latency_measure_result(handle_t h);
|
||||||
|
|
||||||
void report( handle_t h, textBuf::handle_t tbH);
|
void report( handle_t h, textBuf::handle_t tbH);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user