Fix effect preview issues

Fixes bug 1274 and unblocks bug 995.
This commit is contained in:
Steve Daulton 2016-01-02 09:00:40 +00:00
parent 33477fd5d1
commit 46055cde25
7 changed files with 327 additions and 170 deletions

View File

@ -2431,11 +2431,6 @@ bool Effect::IsHidden()
void Effect::Preview(bool dryOnly)
{
if (mIsLinearEffect)
wxLogDebug(wxT("Linear Effect"));
else
wxLogDebug(wxT("Non-linear Effect"));
if (mNumTracks == 0) { // nothing to preview
return;
}
@ -2553,8 +2548,9 @@ void Effect::Preview(bool dryOnly)
playbackTracks.Add(src);
src = (WaveTrack *) iter.Next();
}
if (isNyquist && isGenerator)
t1 = mT1;
// Some effects (Paulstretch) may need to generate more
// than previewLen, so take the min.
t1 = std::min(mT0 + previewLen, mT1);
#ifdef EXPERIMENTAL_MIDI_OUT
NoteTrackArray empty;
@ -2569,7 +2565,6 @@ void Effect::Preview(bool dryOnly)
if (token) {
int previewing = eProgressSuccess;
wxLogDebug(wxT("mT0 %.3f t1 %.3f"),mT0,t1);
// The progress dialog must be deleted before stopping the stream
// to allow events to flow to the app during StopStream processing.
// The progress dialog blocks these events.

View File

@ -334,6 +334,9 @@ protected:
// (such as fade effects) need to know the full selection length.
void SetPreviewFullSelectionFlag(bool previewDurationFlag);
// Use this if the effect needs to know if it is previewing
bool IsPreviewing() { return mIsPreview; }
// Most effects only require selected tracks to be copied for Preview.
// If IncludeNotSelectedPreviewTracks(true), then non-linear effects have
// preview copies of all wave tracks.

View File

@ -26,6 +26,7 @@
#include "../ShuttleGui.h"
#include "../FFT.h"
#include "../widgets/valnum.h"
#include "../Prefs.h"
#include "../WaveTrack.h"
@ -79,8 +80,8 @@ END_EVENT_TABLE()
EffectPaulstretch::EffectPaulstretch()
{
amount = DEF_Amount;
time_resolution = DEF_Time;
mAmount = DEF_Amount;
mTime_resolution = DEF_Time;
SetLinearEffectFlag(true);
}
@ -112,8 +113,8 @@ EffectType EffectPaulstretch::GetType()
bool EffectPaulstretch::GetAutomationParameters(EffectAutomationParameters & parms)
{
parms.WriteFloat(KEY_Amount, amount);
parms.WriteFloat(KEY_Time, time_resolution);
parms.WriteFloat(KEY_Amount, mAmount);
parms.WriteFloat(KEY_Time, mTime_resolution);
return true;
}
@ -123,14 +124,27 @@ bool EffectPaulstretch::SetAutomationParameters(EffectAutomationParameters & par
ReadAndVerifyFloat(Amount);
ReadAndVerifyFloat(Time);
amount = Amount;
time_resolution = Time;
mAmount = Amount;
mTime_resolution = Time;
return true;
}
// Effect implementation
double EffectPaulstretch::CalcPreviewInputLength(double previewLength)
{
// FIXME: Preview is currently at the project rate, but should really be
// at the track rate (bugs 1284 and 852).
int minDuration = GetBufferSize(mProjectRate) * 2 + 1;
// Preview playback may need to be trimmed but this is the smallest selection that we can use.
double minLength = std::max<double>(minDuration / mProjectRate, previewLength / mAmount);
return minLength;
}
bool EffectPaulstretch::Process()
{
CopyInputTracks();
@ -159,11 +173,12 @@ bool EffectPaulstretch::Process()
return true;
}
void EffectPaulstretch::PopulateOrExchange(ShuttleGui & S)
{
S.StartMultiColumn(2, wxALIGN_CENTER);
{
FloatingPointValidator<float> vldAmount(1, &amount);
FloatingPointValidator<float> vldAmount(1, &mAmount);
vldAmount.SetMin(MIN_Amount);
/* i18n-hint: This is how many times longer the sound will be, e.g. applying
@ -172,7 +187,7 @@ void EffectPaulstretch::PopulateOrExchange(ShuttleGui & S)
*/
S.AddTextBox(_("Stretch Factor:"), wxT(""), 10)->SetValidator(vldAmount);
FloatingPointValidator<float> vldTime(3, &time_resolution, NUM_VAL_ONE_TRAILING_ZERO);
FloatingPointValidator<float> vldTime(3, &mTime_resolution, NUM_VAL_ONE_TRAILING_ZERO);
vldTime.SetMin(MIN_Time);
S.AddTextBox(_("Time Resolution (seconds):"), wxT(""), 10)->SetValidator(vldTime);
}
@ -206,33 +221,66 @@ void EffectPaulstretch::OnText(wxCommandEvent & WXUNUSED(evt))
EnableApply(mUIParent->TransferDataFromWindow());
}
int EffectPaulstretch::GetBufferSize(double rate)
{
// Audacity's fft requires a power of 2
float tmp = rate * mTime_resolution / 2.0;
tmp = log(tmp) / log(2.0);
tmp = pow(2.0, floor(tmp + 0.5));
return std::max<int>((int)tmp, 128);
}
bool EffectPaulstretch::ProcessOne(WaveTrack *track,double t0,double t1,int count)
{
int stretch_buf_size;//must be power of 2 (because Audacity's fft requires it)
if (time_resolution<MIN_Time) time_resolution=MIN_Time;
{
float tmp=track->GetRate()*time_resolution*0.5;
tmp=log(tmp)/log(2.0);
tmp=pow(2.0,floor(tmp+0.5));
stretch_buf_size=(int)tmp;
};
if (stretch_buf_size<128) stretch_buf_size=128;
double amount=this->amount;
if (amount<MIN_Amount) amount=MIN_Amount;
int stretch_buf_size = GetBufferSize(track->GetRate());
double amount = this->mAmount;
sampleCount start = track->TimeToLongSamples(t0);
sampleCount end = track->TimeToLongSamples(t1);
sampleCount len = (sampleCount)(end - start);
m_t1=mT1;
int minDuration = stretch_buf_size * 2 + 1;
if (len < minDuration){ //error because the selection is too short
float maxTimeRes = log(len) / log(2.0);
maxTimeRes = pow(2.0, floor(maxTimeRes) + 0.5);
maxTimeRes = maxTimeRes / track->GetRate();
if (this->IsPreviewing()) {
double defaultPreviewLen;
gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &defaultPreviewLen, 6.0);
/* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
if ((minDuration / mProjectRate) < defaultPreviewLen) {
::wxMessageBox (wxString::Format(_("Audio selection too short to preview.\n\n"
"Try increasing the audio selection to at least %.1f seconds,\n"
"or reducing the 'Time Resolution' to less than %.1f seconds."),
(minDuration / track->GetRate()) + 0.05, // round up to 1/10 s.
floor(maxTimeRes * 10.0) / 10.0),
GetName(), wxOK | wxICON_EXCLAMATION);
}
else {
/* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
::wxMessageBox (wxString::Format(_("Unable to Preview.\n\n"
"For the current audio selection, the maximum\n"
"'Time Resolution' is %.1f seconds."),
floor(maxTimeRes * 10.0) / 10.0),
GetName(), wxOK | wxICON_EXCLAMATION);
}
}
else {
/* i18n-hint: 'Time Resolution' is the name of a control in the Paulstretch effect.*/
::wxMessageBox (wxString::Format(_("The 'Time Resolution' is too long for the selection.\n\n"
"Try increasing the audio selection to at least %.1f seconds,\n"
"or reducing the 'Time Resolution' to less than %.1f seconds."),
(minDuration / track->GetRate()) + 0.05, // round up to 1/10 s.
floor(maxTimeRes * 10.0) / 10.0),
GetName(), wxOK | wxICON_EXCLAMATION);
}
if (len<=(stretch_buf_size*2+1)){//error because the selection is too short
/* i18n-hint: This is an effect error message, for the effect named Paulstretch.
* Time Resolution is a parameter of the effect, the translation should match
*/
::wxMessageBox(_("Error in Paulstretch:\nThe selection is too short.\n It must be much longer than the Time Resolution."));
return false;
};
}
double adjust_amount=(double)len/((double)len-((double)stretch_buf_size*2.0));
@ -296,15 +344,11 @@ bool EffectPaulstretch::ProcessOne(WaveTrack *track,double t0,double t1,int coun
delete [] fade_track_smps;
outputTrack->Flush();
track->Clear(t0,t1);
track->Paste(t0,outputTrack);
if (!cancelled){
double flen=t1-t0;
if (s>0) m_t1=t0+flen*(double)outs/(double)(s);
};
bool success = track->Paste(t0,outputTrack);
if (!cancelled && success){
m_t1 = mT0 + outputTrack->GetEndTime();
}
delete stretch;
delete []buffer0;
@ -343,9 +387,8 @@ PaulStretch::PaulStretch(float rap_,int in_bufsize_,float samplerate_)
fft_c[i]=0.0;
fft_s[i]=0.0;
fft_freq[i]=0.0;
};
};
}
}
PaulStretch::~PaulStretch()
{
@ -357,13 +400,13 @@ PaulStretch::~PaulStretch()
delete [] fft_s;
delete [] fft_freq;
delete [] fft_tmp;
};
}
void PaulStretch::set_rap(float newrap)
{
if (rap>=1.0) rap=newrap;
else rap=1.0;
};
}
void PaulStretch::process(float *smps,int nsmps)
{
@ -371,7 +414,7 @@ void PaulStretch::process(float *smps,int nsmps)
if ((smps!=NULL)&&(nsmps!=0)){
if (nsmps>poolsize){
nsmps=poolsize;
};
}
int nleft=poolsize-nsmps;
//move left the samples from the pool to make room for new samples
@ -379,7 +422,7 @@ void PaulStretch::process(float *smps,int nsmps)
//add new samples to the pool
for (int i=0;i<nsmps;i++) in_pool[i+nleft]=smps[i];
};
}
//get the samples from the pool
for (int i=0;i<poolsize;i++) fft_smps[i]=in_pool[i];
@ -399,11 +442,10 @@ void PaulStretch::process(float *smps,int nsmps)
float s=fft_freq[i]*sin(phase);
float c=fft_freq[i]*cos(phase);
fft_c[i]=fft_c[poolsize-i]=c;
fft_s[i]=s;fft_s[poolsize-i]=-s;
};
}
fft_c[0]=fft_s[0]=0.0;
fft_c[poolsize/2]=fft_s[poolsize/2]=0.0;
@ -415,7 +457,7 @@ void PaulStretch::process(float *smps,int nsmps)
if (a>max) max=a;
float b=fabs(fft_smps[i]);
if (b>max2) max2=b;
};
}
//make the output buffer
@ -430,12 +472,11 @@ void PaulStretch::process(float *smps,int nsmps)
float a=(0.5+0.5*cos(i*tmp));
float out=fft_smps[i+out_bufsize]*(1.0-a)+old_out_smp_buf[i]*a;
out_buf[i]=out*(hinv_sqrt2-(1.0-hinv_sqrt2)*cos(i*2.0*tmp))*ampfactor;
};
}
//copy the current output buffer to old buffer
for (int i=0;i<out_bufsize*2;i++) old_out_smp_buf[i]=fft_smps[i];
};
}
int PaulStretch::get_nsamples()
{
@ -447,16 +488,16 @@ int PaulStretch::get_nsamples()
if (remained_samples>=1.0){
ri+=(int)floor(remained_samples);
remained_samples=remained_samples-floor(remained_samples);
};
}
if (ri>poolsize){
ri=poolsize;
};
}
return ri;
};
}
int PaulStretch::get_nsamples_for_fill()
{
return poolsize;
};
}

View File

@ -40,6 +40,7 @@ public:
// Effect implementation
virtual double CalcPreviewInputLength(double previewLength);
virtual bool Process();
virtual void PopulateOrExchange(ShuttleGui & S);
virtual bool TransferDataToWindow();
@ -49,12 +50,13 @@ private:
// EffectPaulstretch implementation
void OnText(wxCommandEvent & evt);
int GetBufferSize(double rate);
bool ProcessOne(WaveTrack *track, double t0, double t1, int count);
private:
float amount;
float time_resolution; //seconds
float mAmount;
float mTime_resolution; //seconds
double m_t1;
DECLARE_EVENT_TABLE();

View File

@ -58,6 +58,13 @@ Param( Compress, double, XO("Compress"), 50.0, 0.0, 99.9,
static const sampleCount DEF_BlendFrameCount = 100;
// Lower bound on the amount of silence to find at a time -- this avoids
// detecting silence repeatedly in low-frequency sounds.
static const double DEF_MinTruncMs = 0.001;
// Typical fraction of total time taken by detection (better to guess low)
const double detectFrac = 0.4;
#include <wx/listimpl.cpp>
WX_DEFINE_LIST(RegionList);
@ -154,6 +161,41 @@ bool EffectTruncSilence::SetAutomationParameters(EffectAutomationParameters & pa
// Effect implementation
double EffectTruncSilence::CalcPreviewInputLength(double previewLength)
{
double inputLength = mT1 - mT0;
double minInputLength = inputLength;
// Master list of silent regions
RegionList silences;
silences.DeleteContents(true);
// Start with the whole selection silent
Region *sel = new Region;
sel->start = mT0;
sel->end = mT1;
silences.push_back(sel);
SelectedTrackListOfKindIterator iter(Track::Wave, mTracks);
int whichTrack = 0;
for (Track *t = iter.First(); t; t = iter.Next()) {
WaveTrack *wt = (WaveTrack *)t;
RegionList trackSilences;
trackSilences.DeleteContents(true);
sampleCount index = wt->TimeToLongSamples(mT0);
sampleCount silentFrame = 0; // length of the current silence
Analyze(silences, trackSilences, wt, &silentFrame, &index, whichTrack, &inputLength, &minInputLength);
whichTrack++;
}
return inputLength;
}
bool EffectTruncSilence::Startup()
{
wxString base = wxT("/Effects/TruncateSilence/");
@ -206,17 +248,9 @@ bool EffectTruncSilence::Startup()
bool EffectTruncSilence::Process()
{
// Typical fraction of total time taken by detection (better to guess low)
const double detectFrac = .4;
// Copy tracks
CopyInputTracks(Track::All);
// Lower bound on the amount of silence to find at a time -- this avoids
// detecting silence repeatedly in low-frequency sounds.
const double minTruncMs = 0.001;
double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex];
// Master list of silent regions; it is responsible for deleting them.
// This list should always be kept in order.
RegionList silences;
@ -237,111 +271,19 @@ bool EffectTruncSilence::Process()
// Smallest silent region to detect in frames
sampleCount minSilenceFrames =
sampleCount(wxMax( mInitialAllowedSilence, minTruncMs) *
wt->GetRate());
sampleCount(wxMax( mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate());
//
// Scan the track for silences
//
RegionList trackSilences;
trackSilences.DeleteContents(true);
sampleCount blockLen = wt->GetMaxBlockSize();
sampleCount start = wt->TimeToLongSamples(mT0);
sampleCount end = wt->TimeToLongSamples(mT1);
// Allocate buffer
float *buffer = new float[blockLen];
sampleCount index = wt->TimeToLongSamples(mT0);
sampleCount silentFrame = 0;
sampleCount index = start;
sampleCount silentFrames = 0;
bool cancelled = false;
// Keep position in overall silences list for optimization
RegionList::iterator rit(silences.begin());
while (index < end)
{
// Show progress dialog, test for cancellation
cancelled = TotalProgress(
detectFrac * (whichTrack + index / (double)end) /
(double)GetNumWaveTracks());
if (cancelled)
{
break;
}
//
// Optimization: if not in a silent region skip ahead to the next one
//
double curTime = wt->LongSamplesToTime(index);
for ( ; rit != silences.end(); ++rit)
{
// Find the first silent region ending after current time
if ((*rit)->end >= curTime)
{
break;
}
}
if (rit == silences.end())
{
// No more regions -- no need to process the rest of the track
break;
}
else if ((*rit)->start > curTime)
{
// End current silent region, skip ahead
if (silentFrames >= minSilenceFrames)
{
Region *r = new Region;
r->start = wt->LongSamplesToTime(index - silentFrames);
r->end = wt->LongSamplesToTime(index);
trackSilences.push_back(r);
}
silentFrames = 0;
index = wt->TimeToLongSamples((*rit)->start);
}
//
// End of optimization
//
// Limit size of current block if we've reached the end
sampleCount count = blockLen;
if ((index + count) > end)
{
count = end - index;
}
// Fill buffer
wt->Get((samplePtr)(buffer), floatSample, index, count);
// Look for silences in current block
for (sampleCount i = 0; i < count; ++i)
{
if (fabs(buffer[i]) < truncDbSilenceThreshold)
{
++silentFrames;
}
else
{
if (silentFrames >= minSilenceFrames)
{
// Record the silent region
Region *r = new Region;
r->start = wt->LongSamplesToTime(index + i - silentFrames);
r->end = wt->LongSamplesToTime(index + i);
trackSilences.push_back(r);
}
silentFrames = 0;
}
}
// Next block
index += count;
}
delete [] buffer;
// Detect silences
bool cancelled = !(Analyze(silences, trackSilences, wt, &silentFrame, &index, whichTrack));
// Buffer has been freed, so we're OK to return if cancelled
if (cancelled)
@ -350,11 +292,11 @@ bool EffectTruncSilence::Process()
return false;
}
if (silentFrames >= minSilenceFrames)
if (silentFrame >= minSilenceFrames)
{
// Track ended in silence -- record region
Region *r = new Region;
r->start = wt->LongSamplesToTime(index - silentFrames);
r->start = wt->LongSamplesToTime(index - silentFrame);
r->end = wt->LongSamplesToTime(index);
trackSilences.push_back(r);
}
@ -480,6 +422,164 @@ bool EffectTruncSilence::Process()
return true;
}
bool EffectTruncSilence::Analyze(RegionList& silenceList,
RegionList& trackSilences,
WaveTrack* wt,
sampleCount* silentFrame,
sampleCount* index,
int whichTrack,
double* inputLength /*= NULL*/,
double* minInputLength /*= NULL*/)
{
// Smallest silent region to detect in frames
sampleCount minSilenceFrames = sampleCount(wxMax( mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate());
double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex];
sampleCount blockLen = wt->GetMaxBlockSize();
sampleCount start = wt->TimeToLongSamples(mT0);
sampleCount end = wt->TimeToLongSamples(mT1);
sampleCount outLength = 0;
double previewLength;
gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &previewLength, 6.0);
// Minimum required length in samples.
const sampleCount previewLen = previewLength * wt->GetRate();
// Keep position in overall silences list for optimization
RegionList::iterator rit(silenceList.begin());
// Allocate buffer
float *buffer = new float[blockLen];
// Loop through current track
while (*index < end) {
if (inputLength && ((outLength >= previewLen) || (*index - start > wt->TimeToLongSamples(*minInputLength)))) {
*inputLength = std::min<double>(*inputLength, *minInputLength);
if (outLength >= previewLen) {
*minInputLength = *inputLength;
}
return true;
}
if (!inputLength) {
// Show progress dialog, test for cancellation
bool cancelled = TotalProgress(
detectFrac * (whichTrack + (*index - start) / (double)(end - start)) /
(double)GetNumWaveTracks());
if (cancelled) {
delete [] buffer;
return false;
}
}
// Optimization: if not in a silent region skip ahead to the next one
double curTime = wt->LongSamplesToTime(*index);
for ( ; rit != silenceList.end(); ++rit) {
// Find the first silent region ending after current time
if ((*rit)->end >= curTime) {
break;
}
}
if (rit == silenceList.end()) {
// No more regions -- no need to process the rest of the track
if (inputLength) {
// Add available samples up to previewLength.
sampleCount remainingTrackSamples = wt->TimeToLongSamples(wt->GetEndTime()) - *index;
sampleCount requiredTrackSamples = previewLen - outLength;
outLength += (remainingTrackSamples > requiredTrackSamples)? requiredTrackSamples : remainingTrackSamples;
}
break;
}
else if ((*rit)->start > curTime) {
// End current silent region, skip ahead
if (*silentFrame >= minSilenceFrames) {
Region *r = new Region;
r->start = wt->LongSamplesToTime(*index - *silentFrame);
r->end = wt->LongSamplesToTime(*index);
trackSilences.push_back(r);
}
*silentFrame = 0;
sampleCount newIndex = wt->TimeToLongSamples((*rit)->start);
if (inputLength) {
sampleCount requiredTrackSamples = previewLen - outLength;
// Add non-silent sample to outLength
outLength += ((newIndex - *index) > requiredTrackSamples)? requiredTrackSamples : newIndex - *index;
}
*index = newIndex;
}
// End of optimization
// Limit size of current block if we've reached the end
sampleCount count = blockLen;
if ((*index + count) > end) {
count = end - *index;
}
// Fill buffer
wt->Get((samplePtr)(buffer), floatSample, *index, count);
// Look for silenceList in current block
for (sampleCount i = 0; i < count; ++i) {
if (inputLength && ((outLength >= previewLen) || (outLength > wt->TimeToLongSamples(*minInputLength)))) {
*inputLength = wt->LongSamplesToTime(*index + i) - wt->LongSamplesToTime(start);
break;
}
if (fabs(buffer[i]) < truncDbSilenceThreshold) {
(*silentFrame)++;
}
else {
sampleCount allowed = 0;
if (*silentFrame >= minSilenceFrames) {
if (inputLength) {
switch (mActionIndex) {
case kTruncate:
outLength += wt->TimeToLongSamples(mTruncLongestAllowedSilence);
break;
case kCompress:
allowed = wt->TimeToLongSamples(mInitialAllowedSilence);
outLength += allowed +
(*silentFrame - allowed) * mSilenceCompressPercent / 100.0;
break;
// default: // Not currently used.
}
}
// Record the silent region
Region *r = new Region;
r->start = wt->LongSamplesToTime(*index + i - *silentFrame);
r->end = wt->LongSamplesToTime(*index + i);
trackSilences.push_back(r);
}
else if (inputLength) { // included as part of non-silence
outLength += *silentFrame;
}
*silentFrame = 0;
if (inputLength) {
++outLength; // Add non-silent sample to outLength
}
}
}
// Next block
*index += count;
}
delete [] buffer;
if (inputLength) {
*inputLength = std::min<double>(*inputLength, *minInputLength);
if (outLength >= previewLen) {
*minInputLength = *inputLength;
}
}
return true;
}
void EffectTruncSilence::PopulateOrExchange(ShuttleGui & S)
{
wxASSERT(kNumActions == WXSIZEOF(kActionStrings));

View File

@ -57,7 +57,21 @@ public:
// Effect implementation
virtual double CalcPreviewInputLength(double previewLength);
virtual bool Startup();
// Analyze a single track to find silences
// If inputLength is not NULL we are calculating the minimum
// amount of input for previewing.
virtual bool Analyze(RegionList &silenceList,
RegionList &trackSilences,
WaveTrack* wt,
sampleCount* silentFrame,
sampleCount* index,
int whichTrack,
double* inputLength = NULL,
double* minInputLength = NULL);
virtual bool Process();
virtual void PopulateOrExchange(ShuttleGui & S);
virtual bool TransferDataToWindow();
@ -68,7 +82,7 @@ private:
//ToDo ... put BlendFrames in Effects, Project, or other class
void BlendFrames(float* buffer, int leftIndex, int rightIndex, int blendFrameCount);
void Intersect(RegionList &dest, const RegionList &src);
void Intersect(RegionList &dest, const RegionList & src);
void OnControlChange(wxCommandEvent & evt);
void UpdateUI();

View File

@ -571,7 +571,9 @@ bool NyquistEffect::Process()
mProps += wxString::Format(wxT("(putprop '*PROJECT* (float %s) 'PREVIEW-DURATION)\n"),
Internat::ToString(previewLen).c_str());
// *PREVIEWP* is true when previewing (better than relying on track view).
wxString isPreviewing = (this->IsPreviewing())? wxT("T") : wxT("NIL");
mProps += wxString::Format(wxT("(setf *PREVIEWP* %s)\n"), isPreviewing.c_str());
mProps += wxString::Format(wxT("(putprop '*SELECTION* (float %s) 'START)\n"),
Internat::ToString(mT0).c_str());