mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-20 22:30:05 +02:00
Adjust for latency as you record, don't wait till after stopping...
... Adjust whenever recording, regardless whether there is any overdub playback too. May adjust by discarding samples when the Track Shift after Record preference is negative (as by default); or, by inserting silence into the start of the recording, when that preference is positive, though that may be less useful. Perhaps the preference control should no longer be called "Track Shift after Record."
This commit is contained in:
parent
bbe54f47ca
commit
a3abacd55d
132
src/AudioIO.cpp
132
src/AudioIO.cpp
@ -2741,12 +2741,8 @@ void AudioIO::StopStream()
|
|||||||
// first track in a project.
|
// first track in a project.
|
||||||
//
|
//
|
||||||
|
|
||||||
double recordingOffset =
|
|
||||||
mLastRecordingOffset +
|
|
||||||
mRecordingSchedule.mLatencyCorrection;
|
|
||||||
|
|
||||||
for (unsigned int i = 0; i < mCaptureTracks.size(); i++) {
|
for (unsigned int i = 0; i < mCaptureTracks.size(); i++) {
|
||||||
// The calls to Flush, and (less likely) Clear and InsertSilence,
|
// The calls to Flush
|
||||||
// may cause exceptions because of exhaustion of disk space.
|
// may cause exceptions because of exhaustion of disk space.
|
||||||
// Stop those exceptions here, or else they propagate through too
|
// Stop those exceptions here, or else they propagate through too
|
||||||
// many parts of Audacity that are not effects or editing
|
// many parts of Audacity that are not effects or editing
|
||||||
@ -2756,8 +2752,6 @@ void AudioIO::StopStream()
|
|||||||
// relying on the guarantee that the track will be left in a flushed
|
// relying on the guarantee that the track will be left in a flushed
|
||||||
// state, though the append buffer may be lost.
|
// state, though the append buffer may be lost.
|
||||||
|
|
||||||
// If the other track operations fail their strong guarantees, then
|
|
||||||
// the shift for latency correction may be skipped.
|
|
||||||
GuardedCall( [&] {
|
GuardedCall( [&] {
|
||||||
WaveTrack* track = mCaptureTracks[i].get();
|
WaveTrack* track = mCaptureTracks[i].get();
|
||||||
|
|
||||||
@ -2766,62 +2760,11 @@ void AudioIO::StopStream()
|
|||||||
// is saved.
|
// is saved.
|
||||||
// See comments in FillBuffers().
|
// See comments in FillBuffers().
|
||||||
track->Flush();
|
track->Flush();
|
||||||
|
|
||||||
if (mPlaybackTracks.size() > 0)
|
|
||||||
{ // only do latency correction if some tracks are being played back
|
|
||||||
WaveTrackArray playbackTracks;
|
|
||||||
AudacityProject *p = GetActiveProject();
|
|
||||||
// we need to get this as mPlaybackTracks does not contain tracks being recorded into
|
|
||||||
playbackTracks = p->GetTracks()->GetWaveTrackArray(false);
|
|
||||||
bool appendRecord = false;
|
|
||||||
for (unsigned int j = 0; j < playbackTracks.size(); j++)
|
|
||||||
{ // find if we are recording into an existing track (append-record)
|
|
||||||
WaveTrack* trackP = playbackTracks[j].get();
|
|
||||||
if( track == trackP )
|
|
||||||
{
|
|
||||||
if( track->GetStartTime() != mT0 ) // in a NEW track if these are equal
|
|
||||||
{
|
|
||||||
appendRecord = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if( appendRecord )
|
|
||||||
{ // append-recording
|
|
||||||
if (recordingOffset < 0)
|
|
||||||
// use STRONG-GUARANTEE
|
|
||||||
track->Clear(mT0, mT0 - recordingOffset); // cut the latency out
|
|
||||||
else
|
|
||||||
// use STRONG-GUARANTEE
|
|
||||||
track->InsertSilence(mT0, recordingOffset); // put silence in
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ // recording into a NEW track
|
|
||||||
// gives NOFAIL-GUARANTEE though we only need STRONG
|
|
||||||
track->SetOffset(track->GetStartTime() + recordingOffset);
|
|
||||||
if(track->GetEndTime() < 0.)
|
|
||||||
{
|
|
||||||
// Bug 96: Only warn for the first track.
|
|
||||||
if( i==0 )
|
|
||||||
{
|
|
||||||
AudacityMessageDialog m(NULL, _(
|
|
||||||
"Latency Correction setting has caused the recorded audio to be hidden before zero.\nAudacity has brought it back to start at zero.\nYou may have to use the Time Shift Tool (<---> or F5) to drag the track to the right place."),
|
|
||||||
_("Latency problem"), wxOK);
|
|
||||||
m.ShowModal();
|
|
||||||
}
|
|
||||||
// gives NOFAIL-GUARANTEE though we only need STRONG
|
|
||||||
track->SetOffset(0.);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &interval : mLostCaptureIntervals) {
|
for (auto &interval : mLostCaptureIntervals) {
|
||||||
auto &start = interval.first;
|
auto &start = interval.first;
|
||||||
if (mPlaybackTracks.size() > 0)
|
|
||||||
// only do latency correction if some tracks are being played back
|
|
||||||
start += recordingOffset;
|
|
||||||
auto duration = interval.second;
|
auto duration = interval.second;
|
||||||
for (auto &track : mCaptureTracks) {
|
for (auto &track : mCaptureTracks) {
|
||||||
GuardedCall([&] {
|
GuardedCall([&] {
|
||||||
@ -4033,13 +3976,11 @@ void AudioIO::FillBuffers()
|
|||||||
// start record buffering
|
// start record buffering
|
||||||
const auto avail = GetCommonlyAvailCapture(); // samples
|
const auto avail = GetCommonlyAvailCapture(); // samples
|
||||||
const auto remainingTime =
|
const auto remainingTime =
|
||||||
std::max(0.0, mRecordingSchedule.Remaining());
|
std::max(0.0, mRecordingSchedule.ToConsume());
|
||||||
// This may be a very big double number:
|
// This may be a very big double number:
|
||||||
const auto remainingSamples = remainingTime * mRate;
|
const auto remainingSamples = remainingTime * mRate;
|
||||||
|
bool latencyCorrected = true;
|
||||||
|
|
||||||
//
|
|
||||||
// Determine how much this will add to captured tracks
|
|
||||||
//
|
|
||||||
double deltat = avail / mRate;
|
double deltat = avail / mRate;
|
||||||
|
|
||||||
if (mAudioThreadShouldCallFillBuffersOnce ||
|
if (mAudioThreadShouldCallFillBuffersOnce ||
|
||||||
@ -4055,17 +3996,44 @@ void AudioIO::FillBuffers()
|
|||||||
sampleFormat trackFormat = mCaptureTracks[i]->GetSampleFormat();
|
sampleFormat trackFormat = mCaptureTracks[i]->GetSampleFormat();
|
||||||
|
|
||||||
AutoSaveFile appendLog;
|
AutoSaveFile appendLog;
|
||||||
|
size_t discarded = 0;
|
||||||
|
|
||||||
|
if (!mRecordingSchedule.mLatencyCorrected) {
|
||||||
|
if(mRecordingSchedule.mLatencyCorrection >= 0) {
|
||||||
|
// Rightward shift
|
||||||
|
// Once only (per track per recording), insert some initial
|
||||||
|
// silence.
|
||||||
|
size_t size = floor(
|
||||||
|
mRecordingSchedule.mLatencyCorrection * mRate * mFactor);
|
||||||
|
SampleBuffer temp(size, trackFormat);
|
||||||
|
ClearSamples(temp.ptr(), trackFormat, 0, size);
|
||||||
|
mCaptureTracks[i]->Append(temp.ptr(), trackFormat,
|
||||||
|
size, 1, &appendLog);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Leftward shift
|
||||||
|
// discard some samples from the ring buffers.
|
||||||
|
size_t size = floor(
|
||||||
|
mRecordingSchedule.ToDiscard() * mRate );
|
||||||
|
discarded = mCaptureBuffers[i]->Discard(size);
|
||||||
|
if (discarded < size)
|
||||||
|
// We need to visit this again to complete the
|
||||||
|
// discarding.
|
||||||
|
latencyCorrected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t toGet = avail - discarded;
|
||||||
if( mFactor == 1.0 )
|
if( mFactor == 1.0 )
|
||||||
{
|
{
|
||||||
SampleBuffer temp(avail, trackFormat);
|
SampleBuffer temp(toGet, trackFormat);
|
||||||
const auto got =
|
const auto got =
|
||||||
mCaptureBuffers[i]->Get(temp.ptr(), trackFormat, avail);
|
mCaptureBuffers[i]->Get(temp.ptr(), trackFormat, toGet);
|
||||||
// wxASSERT(got == avail);
|
// wxASSERT(got == toGet);
|
||||||
// but we can't assert in this thread
|
// but we can't assert in this thread
|
||||||
wxUnusedVar(got);
|
wxUnusedVar(got);
|
||||||
// see comment in second handler about guarantee
|
// see comment in second handler about guarantee
|
||||||
size_t size = avail;
|
size_t size = toGet;
|
||||||
if (double(size) > remainingSamples)
|
if (double(size) > remainingSamples)
|
||||||
size = floor(remainingSamples);
|
size = floor(remainingSamples);
|
||||||
mCaptureTracks[i]-> Append(temp.ptr(), trackFormat,
|
mCaptureTracks[i]-> Append(temp.ptr(), trackFormat,
|
||||||
@ -4074,21 +4042,21 @@ void AudioIO::FillBuffers()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
size_t size = lrint(avail * mFactor);
|
size_t size = lrint(toGet * mFactor);
|
||||||
SampleBuffer temp1(avail, floatSample);
|
SampleBuffer temp1(toGet, floatSample);
|
||||||
SampleBuffer temp2(size, floatSample);
|
SampleBuffer temp2(size, floatSample);
|
||||||
const auto got =
|
const auto got =
|
||||||
mCaptureBuffers[i]->Get(temp1.ptr(), floatSample, avail);
|
mCaptureBuffers[i]->Get(temp1.ptr(), floatSample, toGet);
|
||||||
// wxASSERT(got == avail);
|
// wxASSERT(got == toGet);
|
||||||
// but we can't assert in this thread
|
// but we can't assert in this thread
|
||||||
wxUnusedVar(got);
|
wxUnusedVar(got);
|
||||||
/* we are re-sampling on the fly. The last resampling call
|
/* we are re-sampling on the fly. The last resampling call
|
||||||
* must flush any samples left in the rate conversion buffer
|
* must flush any samples left in the rate conversion buffer
|
||||||
* so that they get recorded
|
* so that they get recorded
|
||||||
*/
|
*/
|
||||||
if (avail > 0 ) {
|
if (toGet > 0 ) {
|
||||||
const auto results =
|
const auto results =
|
||||||
mResample[i]->Process(mFactor, (float *)temp1.ptr(), avail,
|
mResample[i]->Process(mFactor, (float *)temp1.ptr(), toGet,
|
||||||
!IsStreamActive(), (float *)temp2.ptr(), size);
|
!IsStreamActive(), (float *)temp2.ptr(), size);
|
||||||
size = results.second;
|
size = results.second;
|
||||||
// see comment in second handler about guarantee
|
// see comment in second handler about guarantee
|
||||||
@ -4111,7 +4079,9 @@ void AudioIO::FillBuffers()
|
|||||||
}
|
}
|
||||||
} // end loop over capture channels
|
} // end loop over capture channels
|
||||||
|
|
||||||
|
// Now update the recording shedule position
|
||||||
mRecordingSchedule.mPosition += avail / mRate;
|
mRecordingSchedule.mPosition += avail / mRate;
|
||||||
|
mRecordingSchedule.mLatencyCorrected = latencyCorrected;
|
||||||
|
|
||||||
if (mListener && !blockFileLog.IsEmpty())
|
if (mListener && !blockFileLog.IsEmpty())
|
||||||
mListener->OnAudioIONewBlockFiles(blockFileLog);
|
mListener->OnAudioIONewBlockFiles(blockFileLog);
|
||||||
@ -5251,7 +5221,8 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
len < framesPerBuffer) ) {
|
len < framesPerBuffer) ) {
|
||||||
// Assume that any good partial buffer should be written leftmost
|
// Assume that any good partial buffer should be written leftmost
|
||||||
// and zeroes will be padded after; label the zeroes.
|
// and zeroes will be padded after; label the zeroes.
|
||||||
auto start = gAudioIO->mTime + len / gAudioIO->mRate;
|
auto start = gAudioIO->mTime + len / gAudioIO->mRate +
|
||||||
|
gAudioIO->mRecordingSchedule.mLatencyCorrection;
|
||||||
auto duration = (framesPerBuffer - len) / gAudioIO->mRate;
|
auto duration = (framesPerBuffer - len) / gAudioIO->mRate;
|
||||||
auto interval = std::make_pair( start, duration );
|
auto interval = std::make_pair( start, duration );
|
||||||
gAudioIO->mLostCaptureIntervals.push_back( interval );
|
gAudioIO->mLostCaptureIntervals.push_back( interval );
|
||||||
@ -5431,3 +5402,18 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
|
|
||||||
return callbackReturn;
|
return callbackReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double AudioIO::RecordingSchedule::ToConsume() const
|
||||||
|
{
|
||||||
|
return mDuration - mLatencyCorrection - mPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
double AudioIO::RecordingSchedule::ToDiscard() const
|
||||||
|
{
|
||||||
|
if (mLatencyCorrection >= 0)
|
||||||
|
return 0.0;
|
||||||
|
else if (mPosition >= -mLatencyCorrection)
|
||||||
|
return 0.0;
|
||||||
|
else
|
||||||
|
return (-mLatencyCorrection) - mPosition;
|
||||||
|
}
|
||||||
|
@ -820,12 +820,13 @@ private:
|
|||||||
double mLatencyCorrection{};
|
double mLatencyCorrection{};
|
||||||
double mDuration{};
|
double mDuration{};
|
||||||
|
|
||||||
// This is initialized to 0 by the main thread, then updated
|
// These are initialized by the main thread, then updated
|
||||||
// only by the thread calling FillBuffers:
|
// only by the thread calling FillBuffers:
|
||||||
double mPosition{};
|
double mPosition{};
|
||||||
|
bool mLatencyCorrected{};
|
||||||
|
|
||||||
double Remaining() const
|
double ToConsume() const;
|
||||||
{ return mDuration - mLatencyCorrection - mPosition; }
|
double ToDiscard() const;
|
||||||
} mRecordingSchedule{};
|
} mRecordingSchedule{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user