bool EffectNoise::Process() { if (noiseDuration <= 0.0) noiseDuration = sDefaultGenerateLen; //Iterate over each track TrackListIterator iter(mWaveTracks); WaveTrack *track = (WaveTrack *)iter.First(); while (track) { WaveTrack *tmp = mFactory->NewWaveTrack(track->GetSampleFormat(), track->GetRate()); numSamples = (longSampleCount)(noiseDuration * track->GetRate() + 0.5); longSampleCount i = 0; float *data = new float[tmp->GetMaxBlockSize()]; sampleCount block; while(i < numSamples) { block = tmp->GetBestBlockSize(i); if (block > (numSamples - i)) block = numSamples - i; MakeNoise(data, block, track->GetRate(), noiseAmplitude); tmp->Append((samplePtr)data, floatSample, block); i += block; } delete[] data; tmp->Flush(); track->Clear(mT0, mT1); track->Paste(mT0, tmp); delete tmp; //Iterate to the next track track = (WaveTrack *)iter.Next(); } /* save last used values save duration unless value was got from selection, so we save only when user explicitely setup a value */ if (mT1 == mT0) gPrefs->Write(wxT("/CsPresets/NoiseGen_Duration"), noiseDuration); gPrefs->Write(wxT("/CsPresets/NoiseGen_Type"), noiseType); gPrefs->Write(wxT("/CsPresets/NoiseGen_Amp"), noiseAmplitude); mT1 = mT0 + noiseDuration; // Update selection. return true; }
bool EffectToneGen::Process() { if (length <= 0.0) length = sDefaultGenerateLen; //Iterate over each track TrackListIterator iter(mWaveTracks); WaveTrack *track = (WaveTrack *)iter.First(); while (track) { mSample = 0; WaveTrack *tmp = mFactory->NewWaveTrack(track->GetSampleFormat()); mCurRate = track->GetRate(); tmp->SetRate(mCurRate); longSampleCount numSamples = (longSampleCount)(length * mCurRate + 0.5); longSampleCount i = 0; float *data = new float[tmp->GetMaxBlockSize()]; sampleCount block; while(i < numSamples) { block = tmp->GetBestBlockSize(i); if (block > (numSamples - i)) block = numSamples - i; MakeTone(data, block); tmp->Append((samplePtr)data, floatSample, block); i += block; } delete[] data; tmp->Flush(); track->Clear(mT0, mT1); track->Paste(mT0, tmp); delete tmp; //Iterate to the next track track = (WaveTrack *)iter.Next(); } mT1 = mT0 + length; // Update selection. return true; }
bool EffectSilence::Process() { if (length <= 0.0) length = sDefaultGenerateLen; TrackListIterator iter(mWaveTracks); WaveTrack *track = (WaveTrack *)iter.First(); while (track) { WaveTrack *tmp = mFactory->NewWaveTrack(track->GetSampleFormat(), track->GetRate()); tmp->InsertSilence(0.0, length); tmp->Flush(); track->Clear(mT0, mT1); track->Paste(mT0, tmp); delete tmp; //Iterate to the next track track = (WaveTrack *)iter.Next(); } mT1 = mT0 + length; // Update selection. return true; }
bool Generator::Process() { if (GetDuration() < 0.0) return false; // Set up mOutputTracks. // This effect needs Track::All for sync-lock grouping. this->CopyInputTracks(Track::All); // Iterate over the tracks bool bGoodResult = true; int ntrack = 0; TrackListIterator iter(mOutputTracks); Track* t = iter.First(); while (t != NULL) { if (t->GetKind() == Track::Wave && t->GetSelected()) { WaveTrack* track = (WaveTrack*)t; bool editClipCanMove; gPrefs->Read(wxT("/GUI/EditClipCanMove"), &editClipCanMove, true); //if we can't move clips, and we're generating into an empty space, //make sure there's room. if (!editClipCanMove && track->IsEmpty(mT0, mT1+1.0/track->GetRate()) && !track->IsEmpty(mT0, mT0+GetDuration()-(mT1-mT0)-1.0/track->GetRate())) { wxMessageBox( _("There is not enough room available to generate the audio"), _("Error"), wxICON_STOP); Failure(); return false; } if (GetDuration() > 0.0) { AudacityProject *p = GetActiveProject(); // Create a temporary track std::unique_ptr<WaveTrack> tmp( mFactory->NewWaveTrack(track->GetSampleFormat(), track->GetRate()) ); BeforeTrack(*track); BeforeGenerate(); // Fill it with data if (!GenerateTrack(&*tmp, *track, ntrack)) bGoodResult = false; else { // Transfer the data from the temporary track to the actual one tmp->Flush(); SetTimeWarper(new StepTimeWarper(mT0+GetDuration(), GetDuration()-(mT1-mT0))); bGoodResult = track->ClearAndPaste(p->GetSel0(), p->GetSel1(), &*tmp, true, false, GetTimeWarper()); } if (!bGoodResult) { Failure(); return false; } } else { // If the duration is zero, there's no need to actually // generate anything track->Clear(mT0, mT1); } ntrack++; } else if (t->IsSyncLockSelected()) { t->SyncLockAdjust(mT1, mT0 + GetDuration()); } // Move on to the next track t = iter.Next(); } Success(); this->ReplaceProcessedTracks(bGoodResult); mT1 = mT0 + GetDuration(); // Update selection. return true; }
//------------------------- Processing methods ------------------------- bool EffectSineSweepGenerator::Process() { // taken `as is` from Audacity`s Generator.cpp to resolve // a vc++ linking problem... if (mDuration < 0.0) return false; BeforeGenerate(); // Set up mOutputTracks. This effect needs Track::All for grouping this->CopyInputTracks(Track::All); // Iterate over the tracks bool bGoodResult = true; int ntrack = 0; TrackListIterator iter(mOutputTracks); Track* t = iter.First(); while (t != NULL) { if (t->GetKind() == Track::Wave && t->GetSelected()) { WaveTrack* track = (WaveTrack*)t; bool editClipCanMove = true; //gPrefs->Read(wxT("/GUI/EditClipCanMove"), &editClipCanMove, true); //if we can't move clips, and we're generating into an empty space, //make sure there's room. if (!editClipCanMove && track->IsEmpty(mT0, mT1+1.0/track->GetRate()) && !track->IsEmpty(mT0, mT0+mDuration-(mT1-mT0)-1.0/track->GetRate())) { wxMessageBox(_("There is not enough room available to generate the audio"), _("Error"), wxICON_STOP); Failure(); return false; } if (mDuration > 0.0) { // Create a temporary track WaveTrack *tmp = mFactory->NewWaveTrack(track->GetSampleFormat(), track->GetRate()); //BeforeTrack(*track); // Fill it with data if (!GenerateTrack(tmp, *track, ntrack)) bGoodResult = false; else { // Transfer the data from the temporary track to the actual one tmp->Flush(); SetTimeWarper(new AFStepTimeWarper(mT0+mDuration, mDuration-(mT1-mT0))); bGoodResult = track->ClearAndPaste(mT0, mT1, tmp, true, false, GetTimeWarper()); delete tmp; } if (!bGoodResult) { Failure(); return false; } } else { // If the duration is zero, there's no need to actually // generate anything track->Clear(mT0, mT1); } ntrack++; } else if (t->IsSyncLockSelected()) { t->SyncLockAdjust(mT1, mT0 + mDuration); } // Move on to the next track t = iter.Next(); } Success(); this->ReplaceProcessedTracks(bGoodResult); mT1 = mT0 + mDuration; // Update selection. return true; }
bool EffectTruncSilence::Process() { SelectedTrackListOfKindIterator iter(Track::Wave, mTracks); WaveTrack *t; double t0 = mT0; double t1 = mT1; int tndx; int tcount = 0; int fr; // Init using first track t = (WaveTrack *) iter.First(); double rate = t->GetRate(); sampleCount blockLen = t->GetMaxBlockSize(); // Get the left and right bounds for all tracks while (t) { // Make sure all tracks have the same sample rate if (rate != t->GetRate()) { wxMessageBox(_("All tracks must have the same sample rate"), _("Truncate Silence")); return false; } // Count the tracks tcount++; // Set the current bounds to whichever left marker is // greater and whichever right marker is less t0 = wxMax(mT0, t->GetStartTime()); t1 = wxMin(mT1, t->GetEndTime()); // Use the smallest block size of all the tracks blockLen = wxMin(blockLen, t->GetMaxBlockSize()); // Iterate to the next track t = (WaveTrack*) iter.Next(); } // Just a sanity check, really it should be much higher if(blockLen < 4*mBlendFrameCount) blockLen = 4*mBlendFrameCount; // Transform the marker timepoints to samples t = (WaveTrack *) iter.First(); sampleCount start = t->TimeToLongSamples(t0); sampleCount end = t->TimeToLongSamples(t1); // Bigger buffers reduce 'reset' //blockLen *= 8; // Stress-test the logic for cutting samples through block endpoints //blockLen /= 8; // Set thresholds // We have a lower bound on the amount of silence we chop out at a time // to avoid chopping up low frequency sounds. We're good down to 10Hz // if we use 100ms. const float minTruncMs = 1.0f; double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex]; int truncInitialAllowedSilentSamples = int((wxMax( mTruncInitialAllowedSilentMs, minTruncMs) * rate) / 1000.0); int truncLongestAllowedSilentSamples = int((wxMax( mTruncLongestAllowedSilentMs, minTruncMs) * rate) / 1000.0); // Require at least 4 samples for lengths if(truncInitialAllowedSilentSamples < 4) truncInitialAllowedSilentSamples = 4; if(truncLongestAllowedSilentSamples < 4) truncLongestAllowedSilentSamples = 4; // If the cross-fade is longer than the minimum length, // then limit the cross-fade length to the minimum length // This allows us to have reasonable cross-fade by default // and still allow for 1ms minimum lengths if(truncInitialAllowedSilentSamples < mBlendFrameCount) mBlendFrameCount = truncInitialAllowedSilentSamples; if(truncLongestAllowedSilentSamples < mBlendFrameCount) mBlendFrameCount = truncLongestAllowedSilentSamples; // For sake of efficiency, don't let blockLen be less than double the longest silent samples // up until a sane limit of 1Meg samples while((blockLen > 0) && (blockLen < truncLongestAllowedSilentSamples*2) && (blockLen < 1048576)) { blockLen *= 2; } // Don't allow either value to be more than half of the block length if(truncLongestAllowedSilentSamples > blockLen/2) truncLongestAllowedSilentSamples = blockLen/2; if(truncInitialAllowedSilentSamples > truncLongestAllowedSilentSamples) truncInitialAllowedSilentSamples = truncLongestAllowedSilentSamples; // We use the 'longest' variable as additive to the 'initial' variable truncLongestAllowedSilentSamples -= truncInitialAllowedSilentSamples; // Perform the crossfade half-way through the minimum removed silence duration int rampInFrames = (truncInitialAllowedSilentSamples + mBlendFrameCount) / 2; if(rampInFrames > truncInitialAllowedSilentSamples) rampInFrames = truncInitialAllowedSilentSamples; // Allocate buffers float **buffer = new float*[tcount]; for (tndx = 0; tndx < tcount; tndx++) { buffer[tndx] = new float[blockLen]; } // Start processing //Track::All is needed because this effect has clear functionality this->CopyInputTracks(Track::All); // Set up mOutputTracks. SelectedTrackListOfKindIterator iterOut(Track::Wave, mOutputTracks); sampleCount index = start; sampleCount outTrackOffset = start; bool cancelled = false; // Reset bool ignoringFrames = false; bool truncToMinimum = true; // Ignore the initial samples until we get above the noise floor sampleCount consecutiveSilentFrames = 0; sampleCount truncIndex = 0; sampleCount i = 0; sampleCount keep; while (index < end) { // Limit size of current block if we've reached the end sampleCount count = blockLen-i; if ((index + count) > end) { count = end - index; } // Fill the buffers tndx = 0; t = (WaveTrack *) iter.First(); while (t) { t->Get((samplePtr)(buffer[tndx++]+i), floatSample, index, count); t = (WaveTrack *) iter.Next(); } // Shift over to account for samples remaining from prior block sampleCount limit = count+i; // Look for silences in current block for ( ; i < limit; i++) { // Is current frame in all tracks below threshold bool below = true; for (tndx = 0; tndx < tcount; tndx++) { if (fabs(buffer[tndx][i]) >= truncDbSilenceThreshold) { below = false; break; } } // Make sure we cross-fade and output the last silence // so we get a smooth transition into whatever follows the selected region // Also set the 'truncToMinimum' flag so that the last silence is truncated to the minimum amount if(below && ((index+i+1) == end)) { below = false; truncToMinimum = true; } // Count frame if it's below threshold if (below) { consecutiveSilentFrames++; // Ignore this frame (equivalent to cutting it) // otherwise, keep sample to be part of allowed silence if (consecutiveSilentFrames > truncInitialAllowedSilentSamples) { ignoringFrames = true; continue; } } else { if (ignoringFrames == true) { // Scale the consectiveSilentFrames so we keep a silence duration // which is proportional to the original silence up to the limit keep = consecutiveSilentFrames - truncInitialAllowedSilentSamples; keep /= mSilenceCompressRatio; // The first and last samples always get truncated to the minimum amount if(truncToMinimum == true) keep = 0; if(keep > truncLongestAllowedSilentSamples) keep = truncLongestAllowedSilentSamples; if(keep < 0) keep = 0; // Compute the location of the cross-fade to be halfway through the silence // with restriction to the samples we still have available to use rampInFrames = (truncInitialAllowedSilentSamples - keep + mBlendFrameCount) / 2; if(rampInFrames > truncInitialAllowedSilentSamples) rampInFrames = truncInitialAllowedSilentSamples; if(rampInFrames < mBlendFrameCount) rampInFrames = mBlendFrameCount; // Include the cross-fade samples in the count to make the loop logic easier keep += rampInFrames; truncIndex -= rampInFrames; // back up for cross-fade sampleCount curOffset = i - keep; if(curOffset < 0) { // This should never happen, but just in case... keep += curOffset - rampInFrames; if(keep < mBlendFrameCount) keep = mBlendFrameCount; curOffset = 0; } if(truncIndex < 0) { // This should never happen, but just in case... truncIndex = 0; } for (tndx = 0; tndx < tcount; tndx++) { // Cross fade the cut point for (fr = 0; fr < mBlendFrameCount; fr++) { buffer[tndx][truncIndex+fr] = ((mBlendFrameCount-fr)*buffer[tndx][truncIndex+fr] + fr*buffer[tndx][curOffset + fr]) / mBlendFrameCount; } // Append the 'keep' samples, if any for ( ; fr < keep; fr++) { buffer[tndx][truncIndex+fr] = buffer[tndx][curOffset + fr]; } } truncIndex += keep; } consecutiveSilentFrames = 0; ignoringFrames = false; truncToMinimum = false; } // Can get here either because > dbThreshold // or silence duration isn't longer than allowed for (tndx = 0; tndx < tcount; tndx++) { buffer[tndx][truncIndex] = buffer[tndx][i]; } truncIndex++; } // Update tracks if any samples were removed, now or before if (outTrackOffset + truncIndex != index + limit) { // Put updated sample back into output tracks. tndx = 0; t = (WaveTrack *) iterOut.First(); while (t) { t->Set((samplePtr)buffer[tndx++], floatSample, outTrackOffset, truncIndex); t = (WaveTrack *) iterOut.Next(); } } // If currently in a silent section, retain samples for the next pass if(ignoringFrames) { keep = consecutiveSilentFrames - truncInitialAllowedSilentSamples; if(keep > (truncLongestAllowedSilentSamples+mBlendFrameCount)) keep = truncLongestAllowedSilentSamples+mBlendFrameCount; for (tndx = 0; tndx < tcount; tndx++) { for(fr = 0; fr < truncInitialAllowedSilentSamples; fr++) { buffer[tndx][fr] = buffer[tndx][truncIndex-truncInitialAllowedSilentSamples+fr]; } for(fr = 0; fr < keep; fr++) { buffer[tndx][truncInitialAllowedSilentSamples+fr] = buffer[tndx][i-keep+fr]; } } // Update the output index, less what we are retaining for next time outTrackOffset += truncIndex - truncInitialAllowedSilentSamples; // Append the following buffer to the existing data i = consecutiveSilentFrames = truncInitialAllowedSilentSamples + keep; truncIndex = truncInitialAllowedSilentSamples; } else { // Maintain output index outTrackOffset += truncIndex; // Reset the buffer pointers to the beginning i = 0; truncIndex = 0; consecutiveSilentFrames = 0; } // Update progress and bail if user cancelled cancelled = TrackProgress(0, ((double)index / (double)end)); if (cancelled) { break; } // Bump to next block index += count; } AudacityProject *p = GetActiveProject(); if (!p) return false; // Remove stale data at end of output tracks. if (!cancelled && (outTrackOffset < end)) { t = (WaveTrack *) iterOut.First(); if( p->IsSticky() ) t->Clear(outTrackOffset / rate, t1, mOutputTracks); else while(t) { t->Clear(outTrackOffset / rate, t1, mOutputTracks); t = (WaveTrack *) iterOut.Next(); } t1 = outTrackOffset / rate; } // Free buffers for (tndx = 0; tndx < tcount; tndx++) { delete [] buffer[tndx]; } delete [] buffer; mT0 = t0; mT1 = t1; this->ReplaceProcessedTracks(!cancelled); return !cancelled; }
bool EffectNoise::Process() { if (noiseDuration <= 0.0) noiseDuration = sDefaultGenerateLen; //Iterate over each track int ntrack = 0; this->CopyInputWaveTracks(); // Set up mOutputWaveTracks. bool bGoodResult = true; TrackListIterator iter(mOutputWaveTracks); WaveTrack *track = (WaveTrack *)iter.First(); while (track) { WaveTrack *tmp = mFactory->NewWaveTrack(track->GetSampleFormat(), track->GetRate()); numSamples = (longSampleCount)(noiseDuration * track->GetRate() + 0.5); longSampleCount i = 0; float *data = new float[tmp->GetMaxBlockSize()]; sampleCount block; while ((i < numSamples) && bGoodResult) { block = tmp->GetBestBlockSize(i); if (block > (numSamples - i)) block = numSamples - i; MakeNoise(data, block, track->GetRate(), noiseAmplitude); tmp->Append((samplePtr)data, floatSample, block); i += block; //Update the Progress meter if (TrackProgress(ntrack, (double)i / numSamples)) bGoodResult = false; } delete[] data; tmp->Flush(); track->Clear(mT0, mT1); track->Paste(mT0, tmp); delete tmp; if (!bGoodResult) break; //Iterate to the next track ntrack++; track = (WaveTrack *)iter.Next(); } if (bGoodResult) { /* save last used values save duration unless value was got from selection, so we save only when user explicitely setup a value */ if (mT1 == mT0) gPrefs->Write(wxT("/CsPresets/NoiseGen_Duration"), noiseDuration); gPrefs->Write(wxT("/CsPresets/NoiseGen_Type"), noiseType); gPrefs->Write(wxT("/CsPresets/NoiseGen_Amp"), noiseAmplitude); mT1 = mT0 + noiseDuration; // Update selection. } this->ReplaceProcessedWaveTracks(bGoodResult); return bGoodResult; }
void ControlToolBar::OnRecord(wxCommandEvent &evt) { if (gAudioIO->IsBusy()) { mRecord->PopUp(); return; } AudacityProject *p = GetActiveProject(); if( evt.GetInt() == 1 ) // used when called by keyboard shortcut. Default (0) ignored. mRecord->SetShift(true); if( evt.GetInt() == 2 ) mRecord->SetShift(false); SetRecord(true, mRecord->WasShiftDown()); if (p) { TrackList *t = p->GetTracks(); TrackListIterator it(t); if(it.First() == NULL) mRecord->SetShift(false); double t0 = p->GetSel0(); double t1 = p->GetSel1(); if (t1 == t0) t1 = 1000000000.0; // record for a long, long time (tens of years) /* TODO: set up stereo tracks if that is how the user has set up * their preferences, and choose sample format based on prefs */ WaveTrackArray newRecordingTracks, playbackTracks; #ifdef EXPERIMENTAL_MIDI_OUT NoteTrackArray midiTracks; #endif bool duplex; gPrefs->Read(wxT("/AudioIO/Duplex"), &duplex, true); if(duplex){ playbackTracks = t->GetWaveTrackArray(false); #ifdef EXPERIMENTAL_MIDI_OUT midiTracks = t->GetNoteTrackArray(false); #endif } else { playbackTracks = WaveTrackArray(); #ifdef EXPERIMENTAL_MIDI_OUT midiTracks = NoteTrackArray(); #endif } // If SHIFT key was down, the user wants append to tracks int recordingChannels = 0; bool shifted = mRecord->WasShiftDown(); if (shifted) { bool sel = false; double allt0 = t0; // Find the maximum end time of selected and all wave tracks // Find whether any tracks were selected. (If any are selected, // record only into them; else if tracks exist, record into all.) for (Track *tt = it.First(); tt; tt = it.Next()) { if (tt->GetKind() == Track::Wave) { WaveTrack *wt = static_cast<WaveTrack *>(tt); if (wt->GetEndTime() > allt0) { allt0 = wt->GetEndTime(); } if (tt->GetSelected()) { sel = true; if (wt->GetEndTime() > t0) { t0 = wt->GetEndTime(); } } } } // Use end time of all wave tracks if none selected if (!sel) { t0 = allt0; } // Pad selected/all wave tracks to make them all the same length // Remove recording tracks from the list of tracks for duplex ("overdub") // playback. for (Track *tt = it.First(); tt; tt = it.Next()) { if (tt->GetKind() == Track::Wave && (tt->GetSelected() || !sel)) { WaveTrack *wt = static_cast<WaveTrack *>(tt); if (duplex) playbackTracks.Remove(wt); t1 = wt->GetEndTime(); if (t1 < t0) { WaveTrack *newTrack = p->GetTrackFactory()->NewWaveTrack(); newTrack->InsertSilence(0.0, t0 - t1); newTrack->Flush(); wt->Clear(t1, t0); bool bResult = wt->Paste(t1, newTrack); wxASSERT(bResult); // TO DO: Actually handle this. delete newTrack; } newRecordingTracks.Add(wt); } } t1 = 1000000000.0; // record for a long, long time (tens of years) } else { recordingChannels = gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2); for (int c = 0; c < recordingChannels; c++) { WaveTrack *newTrack = p->GetTrackFactory()->NewWaveTrack(); newTrack->SetOffset(t0); if (recordingChannels > 2) newTrack->SetMinimized(true); if (recordingChannels == 2) { if (c == 0) { newTrack->SetChannel(Track::LeftChannel); newTrack->SetLinked(true); } else { newTrack->SetChannel(Track::RightChannel); } } else { newTrack->SetChannel( Track::MonoChannel ); } newRecordingTracks.Add(newTrack); } // msmeyer: StartStream calls a callback which triggers auto-save, so // we add the tracks where recording is done into now. We remove them // later if starting the stream fails for (unsigned int i = 0; i < newRecordingTracks.GetCount(); i++) t->Add(newRecordingTracks[i]); } //Automated Input Level Adjustment Initialization #ifdef AUTOMATED_INPUT_LEVEL_ADJUSTMENT gAudioIO->AILAInitialize(); #endif int token = gAudioIO->StartStream(playbackTracks, newRecordingTracks, #ifdef EXPERIMENTAL_MIDI_OUT midiTracks, #endif t->GetTimeTrack(), p->GetRate(), t0, t1, p); bool success = (token != 0); if (success) { p->SetAudioIOToken(token); mBusyProject = p; } else { // msmeyer: Delete recently added tracks if opening stream fails if (!shifted) { for (unsigned int i = 0; i < newRecordingTracks.GetCount(); i++) { t->Remove(newRecordingTracks[i]); delete newRecordingTracks[i]; } } // msmeyer: Show error message if stream could not be opened wxMessageBox(_("Error while opening sound device. Please check the recording device settings and the project sample rate."), _("Error"), wxOK | wxICON_EXCLAMATION, this); SetPlay(false); SetStop(false); SetRecord(false); } } }
void ControlToolBar::OnRecord(wxCommandEvent &evt) { auto doubleClicked = mRecord->IsDoubleClicked(); mRecord->ClearDoubleClicked(); if (doubleClicked) { // Display a fixed recording head while scrolling the waves continuously. // If you overdub, you may want to anticipate some context in existing tracks, // so center the head. If not, put it rightmost to display as much wave as we can. const auto project = GetActiveProject(); bool duplex; gPrefs->Read(wxT("/AudioIO/Duplex"), &duplex, true); if (duplex) { // See if there is really anything being overdubbed if (gAudioIO->GetNumPlaybackChannels() == 0) // No. duplex = false; } using Mode = AudacityProject::PlaybackScroller::Mode; project->GetPlaybackScroller().Activate(duplex ? Mode::Centered : Mode::Right); return; } if (gAudioIO->IsBusy()) { if (!CanStopAudioStream() || 0 == gAudioIO->GetNumCaptureChannels()) mRecord->PopUp(); else mRecord->PushDown(); return; } AudacityProject *p = GetActiveProject(); if( evt.GetInt() == 1 ) // used when called by keyboard shortcut. Default (0) ignored. mRecord->SetShift(true); if( evt.GetInt() == 2 ) mRecord->SetShift(false); SetRecord(true, mRecord->WasShiftDown()); if (p) { TrackList *trackList = p->GetTracks(); TrackListIterator it(trackList); if(it.First() == NULL) mRecord->SetShift(false); double t0 = p->GetSel0(); double t1 = p->GetSel1(); if (t1 == t0) t1 = 1000000000.0; // record for a long, long time (tens of years) /* TODO: set up stereo tracks if that is how the user has set up * their preferences, and choose sample format based on prefs */ WaveTrackArray newRecordingTracks, playbackTracks; #ifdef EXPERIMENTAL_MIDI_OUT NoteTrackArray midiTracks; #endif bool duplex; gPrefs->Read(wxT("/AudioIO/Duplex"), &duplex, true); if(duplex){ playbackTracks = trackList->GetWaveTrackArray(false); #ifdef EXPERIMENTAL_MIDI_OUT midiTracks = trackList->GetNoteTrackArray(false); #endif } else { playbackTracks = WaveTrackArray(); #ifdef EXPERIMENTAL_MIDI_OUT midiTracks = NoteTrackArray(); #endif } // If SHIFT key was down, the user wants append to tracks int recordingChannels = 0; TrackList tracksCopy{}; bool tracksCopied = false; bool shifted = mRecord->WasShiftDown(); if (shifted) { bool sel = false; double allt0 = t0; // Find the maximum end time of selected and all wave tracks // Find whether any tracks were selected. (If any are selected, // record only into them; else if tracks exist, record into all.) for (Track *tt = it.First(); tt; tt = it.Next()) { if (tt->GetKind() == Track::Wave) { WaveTrack *wt = static_cast<WaveTrack *>(tt); if (wt->GetEndTime() > allt0) { allt0 = wt->GetEndTime(); } if (tt->GetSelected()) { sel = true; if (wt->GetEndTime() > t0) { t0 = wt->GetEndTime(); } } } } // Use end time of all wave tracks if none selected if (!sel) { t0 = allt0; } // Pad selected/all wave tracks to make them all the same length // Remove recording tracks from the list of tracks for duplex ("overdub") // playback. for (Track *tt = it.First(); tt; tt = it.Next()) { if (tt->GetKind() == Track::Wave && (tt->GetSelected() || !sel)) { WaveTrack *wt = static_cast<WaveTrack *>(tt); if (duplex) { auto end = playbackTracks.end(); auto it = std::find(playbackTracks.begin(), end, wt); if (it != end) playbackTracks.erase(it); } t1 = wt->GetEndTime(); if (t1 < t0) { if (!tracksCopied) { tracksCopied = true; tracksCopy = *trackList; } auto newTrack = p->GetTrackFactory()->NewWaveTrack(); newTrack->InsertSilence(0.0, t0 - t1); newTrack->Flush(); wt->Clear(t1, t0); bool bResult = wt->Paste(t1, newTrack.get()); wxASSERT(bResult); // TO DO: Actually handle this. wxUnusedVar(bResult); } newRecordingTracks.push_back(wt); } } t1 = 1000000000.0; // record for a long, long time (tens of years) } else { bool recordingNameCustom, useTrackNumber, useDateStamp, useTimeStamp; wxString defaultTrackName, defaultRecordingTrackName; int numTracks = 0; for (Track *tt = it.First(); tt; tt = it.Next()) { if (tt->GetKind() == Track::Wave && !tt->GetLinked()) numTracks++; } numTracks++; recordingChannels = gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2); gPrefs->Read(wxT("/GUI/TrackNames/RecordingNameCustom"), &recordingNameCustom, false); gPrefs->Read(wxT("/GUI/TrackNames/TrackNumber"), &useTrackNumber, false); gPrefs->Read(wxT("/GUI/TrackNames/DateStamp"), &useDateStamp, false); gPrefs->Read(wxT("/GUI/TrackNames/TimeStamp"), &useTimeStamp, false); /* i18n-hint: The default name for an audio track. */ gPrefs->Read(wxT("/GUI/TrackNames/DefaultTrackName"),&defaultTrackName, _("Audio Track")); gPrefs->Read(wxT("/GUI/TrackNames/RecodingTrackName"), &defaultRecordingTrackName, defaultTrackName); wxString baseTrackName = recordingNameCustom? defaultRecordingTrackName : defaultTrackName; for (int c = 0; c < recordingChannels; c++) { auto newTrack = p->GetTrackFactory()->NewWaveTrack(); newTrack->SetOffset(t0); wxString nameSuffix = wxString(wxT("")); if (useTrackNumber) { nameSuffix += wxString::Format(wxT("%d"), numTracks + c); } if (useDateStamp) { if (!nameSuffix.IsEmpty()) { nameSuffix += wxT("_"); } nameSuffix += wxDateTime::Now().FormatISODate(); } if (useTimeStamp) { if (!nameSuffix.IsEmpty()) { nameSuffix += wxT("_"); } nameSuffix += wxDateTime::Now().FormatISOTime(); } // ISO standard would be nice, but ":" is unsafe for file name. nameSuffix.Replace(wxT(":"), wxT("-")); if (baseTrackName.IsEmpty()) { newTrack->SetName(nameSuffix); } else if (nameSuffix.IsEmpty()) { newTrack->SetName(baseTrackName); } else { newTrack->SetName(baseTrackName + wxT("_") + nameSuffix); } if (recordingChannels > 2) newTrack->SetMinimized(true); if (recordingChannels == 2) { if (c == 0) { newTrack->SetChannel(Track::LeftChannel); newTrack->SetLinked(true); } else { newTrack->SetChannel(Track::RightChannel); } } else { newTrack->SetChannel( Track::MonoChannel ); } // Let the list hold the track, and keep a pointer to it newRecordingTracks.push_back( static_cast<WaveTrack*>( trackList->Add( std::move(newTrack)))); } } //Automated Input Level Adjustment Initialization #ifdef EXPERIMENTAL_AUTOMATED_INPUT_LEVEL_ADJUSTMENT gAudioIO->AILAInitialize(); #endif AudioIOStartStreamOptions options(p->GetDefaultPlayOptions()); int token = gAudioIO->StartStream(playbackTracks, newRecordingTracks, #ifdef EXPERIMENTAL_MIDI_OUT midiTracks, #endif t0, t1, options); bool success = (token != 0); if (success) { p->SetAudioIOToken(token); mBusyProject = p; } else { if (shifted) { // Restore the tracks to remove any inserted silence if (tracksCopied) *trackList = std::move(tracksCopy); } else { // msmeyer: Delete recently added tracks if opening stream fails for (unsigned int i = 0; i < newRecordingTracks.size(); i++) { trackList->Remove(newRecordingTracks[i]); } } // msmeyer: Show error message if stream could not be opened wxMessageBox(_("Error while opening sound device. Please check the recording device settings and the project sample rate."), _("Error"), wxOK | wxICON_EXCLAMATION, this); SetPlay(false); SetStop(false); SetRecord(false); } } UpdateStatusBar(GetActiveProject()); }
bool EffectTruncSilence::Process() { TrackListIterator iter(mWaveTracks); WaveTrack *t; double t0 = mT0; double t1 = mT1; int tndx; int tcount = 0; // Init using first track t = (WaveTrack *) iter.First(); double rate = t->GetRate(); sampleCount blockLen = t->GetMaxBlockSize(); // Get the left and right bounds for all tracks while (t) { // Make sure all tracks have the same sample rate if (rate != t->GetRate()) { wxMessageBox(_("All tracks must have the same sample rate"), _("Truncate Silence")); return false; } // Count the tracks tcount++; // Set the current bounds to whichever left marker is // greater and whichever right marker is less t0 = wxMax(mT0, t->GetStartTime()); t1 = wxMin(mT1, t->GetEndTime()); // Use the smallest block size of all the tracks blockLen = wxMin(blockLen, t->GetMaxBlockSize()); // Iterate to the next track t = (WaveTrack*) iter.Next(); } // Transform the marker timepoints to samples t = (WaveTrack *) iter.First(); longSampleCount start = t->TimeToLongSamples(t0); longSampleCount end = t->TimeToLongSamples(t1); // Bigger buffers reduce 'reset' blockLen *= 8; // Allocate buffers float **buffer = new float*[tcount]; for (tndx = 0; tndx < tcount; tndx++) { buffer[tndx] = new float[blockLen]; } // Set thresholds // We have a lower bound on the amount of silence we chop out at a time // to avoid chopping up low frequency sounds. We're good down to 10Hz // if we use 100ms. const float minTruncMs = 1.0f; double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex]; int truncLongestAllowedSilentSamples = int((wxMax( mTruncLongestAllowedSilentMs, minTruncMs) * rate) / 1000.0); // Figure out number of frames for ramping int quarterSecondFrames = int((rate * QUARTER_SECOND_MS) / 1000.0); int rampInFrames = (truncLongestAllowedSilentSamples / 4); if (rampInFrames > quarterSecondFrames) { rampInFrames = quarterSecondFrames; } // Start processing this->CopyInputWaveTracks(); // Set up mOutputWaveTracks. TrackListIterator iterOut(mOutputWaveTracks); longSampleCount index = start; longSampleCount outTrackOffset = start; bool cancelled = false; while (index < end) { // Limit size of current block if we've reached the end sampleCount limit = blockLen; if ((index + blockLen) > end) { limit = end - index; } // Fill the buffers tndx = 0; t = (WaveTrack *) iter.First(); while (t) { t->Get((samplePtr)buffer[tndx++], floatSample, index, blockLen); t = (WaveTrack *) iter.Next(); } // Reset bool ignoringFrames = false; sampleCount consecutiveSilentFrames = 0; sampleCount truncIndex = 0; // Look for silences in current block for (sampleCount i = 0; i < limit; i++) { // Is current frame in all tracks below threshold bool below = true; for (tndx = 0; tndx < tcount; tndx++) { if (fabs(buffer[tndx][i]) >= truncDbSilenceThreshold) { below = false; break; } } // Count frame if it's below threshold if (below) { consecutiveSilentFrames++; // Ignore this frame (equivalent to cutting it) // otherwise, keep sample to be part of allowed silence if (consecutiveSilentFrames > truncLongestAllowedSilentSamples) { ignoringFrames = true; continue; } } else { if (ignoringFrames == true) { sampleCount curOffset = i - rampInFrames; truncIndex -= rampInFrames; // backup into ignored frames for (tndx = 0; tndx < tcount; tndx++) { sampleCount trunci = truncIndex; for (int fr = 0; fr < rampInFrames; fr++) { buffer[tndx][trunci++] = buffer[tndx][curOffset + fr]; } if(((trunci - rampInFrames) - mBlendFrameCount) >= 0) { BlendFrames(buffer[tndx], mBlendFrameCount, ((trunci - rampInFrames) - mBlendFrameCount), ((i - rampInFrames) - mBlendFrameCount)); } } truncIndex += rampInFrames; } consecutiveSilentFrames = 0; ignoringFrames = false; } // Can get here either because > dbThreshold // or silence duration isn't longer than allowed for (tndx = 0; tndx < tcount; tndx++) { buffer[tndx][truncIndex] = buffer[tndx][i]; } truncIndex++; } // Update tracks if any samples were removed if (truncIndex < limit) { // Put updated sample back into output tracks. tndx = 0; t = (WaveTrack *) iterOut.First(); while (t) { t->Set((samplePtr)buffer[tndx++], floatSample, outTrackOffset, truncIndex); t = (WaveTrack *) iterOut.Next(); } } // Maintain output index outTrackOffset += truncIndex; // Update progress and bail if user cancelled cancelled = TrackProgress(0, ((double)index / (double)end)); if (cancelled) { break; } // Bump to next block index += limit; } // Remove stale data at end of output tracks. if (!cancelled && (outTrackOffset < end)) { t = (WaveTrack *) iterOut.First(); while (t) { t->Clear(outTrackOffset / rate, t1); t = (WaveTrack *) iterOut.Next(); } t1 = outTrackOffset / rate; } // Free buffers for (tndx = 0; tndx < tcount; tndx++) { delete [] buffer[tndx]; } delete [] buffer; mT0 = t0; mT1 = t1; this->ReplaceProcessedWaveTracks(!cancelled); return !cancelled; }
bool EffectTruncSilence::Process() { // Typical fraction of total time taken by detection (better to guess low) const double detectFrac = .4; // Copy tracks this->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; silences.DeleteContents(true); // Start with the whole selection silent Region *sel = new Region; sel->start = mT0; sel->end = mT1; silences.push_back(sel); // Remove non-silent regions in each track SelectedTrackListOfKindIterator iter(Track::Wave, mTracks); int whichTrack = 0; for (Track *t = iter.First(); t; t = iter.Next()) { WaveTrack *wt = (WaveTrack *)t; // Smallest silent region to detect in frames sampleCount minSilenceFrames = sampleCount(wxMax( mInitialAllowedSilence, 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 = 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; // Buffer has been freed, so we're OK to return if cancelled if (cancelled) { ReplaceProcessedTracks(false); return false; } if (silentFrames >= minSilenceFrames) { // Track ended in silence -- record region Region *r = new Region; r->start = wt->LongSamplesToTime(index - silentFrames); r->end = wt->LongSamplesToTime(index); trackSilences.push_back(r); } // Intersect with the overall silent region list Intersect(silences, trackSilences); whichTrack++; } // // Now remove the silent regions from all selected / sync-lock selected tracks. // // Loop over detected regions in reverse (so cuts don't change time values // down the line) int whichReg = 0; RegionList::reverse_iterator rit; double totalCutLen = 0.0; // For cutting selection at the end for (rit = silences.rbegin(); rit != silences.rend(); ++rit) { Region *r = *rit; // Progress dialog and cancellation. Do additional cleanup before return. if (TotalProgress(detectFrac + (1 - detectFrac) * whichReg / (double)silences.size())) { ReplaceProcessedTracks(false); return false; } // Intersection may create regions smaller than allowed; ignore them. // Allow one nanosecond extra for consistent results with exact milliseconds of allowed silence. if ((r->end - r->start) < (mInitialAllowedSilence - 0.000000001)) continue; // Find new silence length as requested double inLength = r->end - r->start; double outLength; switch (mProcessIndex) { case 0: outLength = wxMin(mTruncLongestAllowedSilence, inLength); break; case 1: outLength = mInitialAllowedSilence + (inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0; break; default: // Not currently used. outLength = wxMin(mInitialAllowedSilence + (inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0, mTruncLongestAllowedSilence); } double cutLen = inLength - outLength; totalCutLen += cutLen; TrackListIterator iterOut(mOutputTracks); for (Track *t = iterOut.First(); t; t = iterOut.Next()) { // Don't waste time past the end of a track if (t->GetEndTime() < r->start) continue; if (t->GetKind() == Track::Wave && ( t->GetSelected() || t->IsSyncLockSelected())) { // In WaveTracks, clear with a cross-fade WaveTrack *wt = (WaveTrack *)t; sampleCount blendFrames = mBlendFrameCount; double cutStart = (r->start + r->end - cutLen) / 2; double cutEnd = cutStart + cutLen; // Round start/end times to frame boundaries cutStart = wt->LongSamplesToTime(wt->TimeToLongSamples(cutStart)); cutEnd = wt->LongSamplesToTime(wt->TimeToLongSamples(cutEnd)); // Make sure the cross-fade does not affect non-silent frames if (wt->LongSamplesToTime(blendFrames) > inLength) { blendFrames = wt->TimeToLongSamples(inLength); } // Perform cross-fade in memory float *buf1 = new float[blendFrames]; float *buf2 = new float[blendFrames]; sampleCount t1 = wt->TimeToLongSamples(cutStart) - blendFrames / 2; sampleCount t2 = wt->TimeToLongSamples(cutEnd) - blendFrames / 2; wt->Get((samplePtr)buf1, floatSample, t1, blendFrames); wt->Get((samplePtr)buf2, floatSample, t2, blendFrames); for (sampleCount i = 0; i < blendFrames; ++i) { buf1[i] = ((blendFrames-i) * buf1[i] + i * buf2[i]) / (double)blendFrames; } // Perform the cut wt->Clear(cutStart, cutEnd); // Write cross-faded data wt->Set((samplePtr)buf1, floatSample, t1, blendFrames); delete [] buf1; delete [] buf2; } else if (t->GetSelected() || t->IsSyncLockSelected()) { // Non-wave tracks: just do a sync-lock adjust double cutStart = (r->start + r->end - cutLen) / 2; double cutEnd = cutStart + cutLen; t->SyncLockAdjust(cutEnd, cutStart); } } ++whichReg; } mT1 -= totalCutLen; ReplaceProcessedTracks(true); return true; }
bool EffectDtmf::Process() { if (dtmfDuration <= 0.0) return false; //Iterate over each track TrackListIterator iter(mWaveTracks); WaveTrack *track = (WaveTrack *)iter.First(); while (track) { // new tmp track, to fill with dtmf sequence // we will build the track by adding a tone, then a silence, next tone, and so on... WaveTrack *tmp = mFactory->NewWaveTrack(track->GetSampleFormat(), track->GetRate()); // all dtmf sequence durations in samples from seconds numSamplesSequence = (longSampleCount)(dtmfDuration * track->GetRate() + 0.5); numSamplesTone = (longSampleCount)(dtmfTone * track->GetRate() + 0.5); numSamplesSilence = (longSampleCount)(dtmfSilence * track->GetRate() + 0.5); // recalculate the sum, and spread the difference - due to approximations. // Since diff should be in the order of "some" samples, a division (resulting in zero) // is not sufficient, so we add the additional remaining samples in each tone/silence block, // at least until available. int diff = numSamplesSequence - (dtmfNTones*numSamplesTone) - (dtmfNTones-1)*numSamplesSilence; if (diff>dtmfNTones) { // in this case, both these values would change, so it makes sense to recalculate diff // otherwise just keep the value we already have // should always be the case that dtmfNTones>1, as if 0, we don't even start processing, // and with 1 there is no difference to spread (no silence slot)... wxASSERT(dtmfNTones > 1); numSamplesTone += (diff/(dtmfNTones)); numSamplesSilence += (diff/(dtmfNTones-1)); diff = numSamplesSequence - (dtmfNTones*numSamplesTone) - (dtmfNTones-1)*numSamplesSilence; } // this var will be used as extra samples distributor int extra=0; longSampleCount i = 0; longSampleCount j = 0; int n=0; // pointer to string in dtmfString sampleCount block; bool isTone = true; float *data = new float[tmp->GetMaxBlockSize()]; // for the whole dtmf sequence, we will be generating either tone or silence // according to a bool value, and this might be done in small chunks of size // 'block', as a single tone might sometimes be larger than the block // tone and silence generally have different duration, thus two generation blocks // // Note: to overcome a 'clicking' noise introduced by the abrupt transition from/to // silence, I added a fade in/out of 1/250th of a second (4ms). This can still be // tweaked but gives excellent results at 44.1kHz: I haven't tried other freqs. // A problem might be if the tone duration is very short (<10ms)... (?) // // One more problem is to deal with the approximations done when calculating the duration // of both tone and silence: in some cases the final sum might not be same as the initial // duration. So, to overcome this, we had a redistribution block up, and now we will spread // the remaining samples in every bin in order to achieve the full duration: test case was // to generate an 11 tone DTMF sequence, in 4 seconds, and with DutyCycle=75%: after generation // you ended up with 3.999s or in other units: 3 seconds and 44097 samples. // while(i < numSamplesSequence) { if (isTone) // generate tone { // the statement takes care of extracting one sample from the diff bin and // adding it into the tone block until depletion extra=(diff-- > 0?1:0); for(j=0; j < numSamplesTone+extra; j+=block) { block = tmp->GetBestBlockSize(j); if (block > (numSamplesTone+extra - j)) block = numSamplesTone+extra - j; // generate the tone and append MakeDtmfTone(data, block, track->GetRate(), dtmfString[n], j, numSamplesTone); tmp->Append((samplePtr)data, floatSample, block); } i += numSamplesTone; n++; if(n>=dtmfNTones)break; } else // generate silence { // the statement takes care of extracting one sample from the diff bin and // adding it into the silence block until depletion extra=(diff-- > 0?1:0); for(j=0; j < numSamplesSilence+extra; j+=block) { block = tmp->GetBestBlockSize(j); if (block > (numSamplesSilence+extra - j)) block = numSamplesSilence+extra - j; // generate silence and append memset(data, 0, sizeof(float)*block); tmp->Append((samplePtr)data, floatSample, block); } i += numSamplesSilence; } // flip flag isTone=!isTone; } // finished the whole dtmf sequence delete[] data; tmp->Flush(); track->Clear(mT0, mT1); track->Paste(mT0, tmp); delete tmp; //Iterate to the next track track = (WaveTrack *)iter.Next(); } /* save last used values save duration unless value was got from selection, so we save only when user explicitely setup a value */ if (mT1 == mT0) gPrefs->Write(wxT("/CsPresets/DtmfGen_SequenceDuration"), dtmfDuration); gPrefs->Write(wxT("/CsPresets/DtmfGen_String"), dtmfString); gPrefs->Write(wxT("/CsPresets/DtmfGen_DutyCycle"), dtmfDutyCycle); // Update selection: this is not accurate if my calculations are wrong. // To validate, once the effect is done, unselect, and select all, then // see what the selection length is being reported (in sec,ms,samples) mT1 = mT0 + dtmfDuration; return true; }
void ControlToolBar::OnRecord(wxCommandEvent &evt) { if (gAudioIO->IsBusy()) { mRecord->PopUp(); return; } AudacityProject *p = GetActiveProject(); if (p && p->GetCleanSpeechMode()) { size_t numProjects = gAudacityProjects.Count(); bool tracks = (p && !p->GetTracks()->IsEmpty()); if (tracks || (numProjects > 1)) { wxMessageBox(_("Recording in CleanSpeech mode is not possible when a track, or more than one project, is already open."), _("Recording not permitted"), wxOK | wxICON_INFORMATION, this); mRecord->PopUp(); mRecord->Disable(); return; } } if( evt.GetInt() == 1 ) // used when called by keyboard shortcut. Default (0) ignored. mRecord->SetShift(true); if( evt.GetInt() == 2 ) mRecord->SetShift(false); SetRecord(true); if (p) { TrackList *t = p->GetTracks(); TrackListIterator it(t); if(it.First() == NULL) mRecord->SetShift(false); double t0 = p->GetSel0(); double t1 = p->GetSel1(); if (t1 == t0) t1 = 1000000000.0; // record for a long, long time (tens of years) /* TODO: set up stereo tracks if that is how the user has set up * their preferences, and choose sample format based on prefs */ WaveTrackArray newRecordingTracks, playbackTracks; /* REQUIRES PORTMIDI */ // NoteTrackArray midiTracks; bool duplex; gPrefs->Read(wxT("/AudioIO/Duplex"), &duplex, true); if(duplex){ playbackTracks = t->GetWaveTrackArray(false); /* REQUIRES PORTMIDI */ // midiTracks = t->GetNoteTrackArray(false); } else { playbackTracks = WaveTrackArray(); /* REQUIRES PORTMIDI */ // midiTracks = NoteTrackArray(); } // If SHIFT key was down, the user wants append to tracks int recordingChannels = 0; bool shifted = mRecord->WasShiftDown(); if (shifted) { TrackListIterator it(t); WaveTrack *wt; bool sel = false; double allt0 = t0; // Find the maximum end time of selected and all wave tracks for (Track *tt = it.First(); tt; tt = it.Next()) { if (tt->GetKind() == Track::Wave) { wt = (WaveTrack *)tt; if (wt->GetEndTime() > allt0) { allt0 = wt->GetEndTime(); } if (tt->GetSelected()) { sel = true; if (duplex) playbackTracks.Remove(wt); if (wt->GetEndTime() > t0) { t0 = wt->GetEndTime(); } } } } // Use end time of all wave tracks if none selected if (!sel) { t0 = allt0; } // Pad selected/all wave tracks to make them all the same length for (Track *tt = it.First(); tt; tt = it.Next()) { if (tt->GetKind() == Track::Wave && (tt->GetSelected() || !sel)) { wt = (WaveTrack *)tt; t1 = wt->GetEndTime(); if (t1 < t0) { WaveTrack *newTrack = p->GetTrackFactory()->NewWaveTrack(); newTrack->InsertSilence(0.0, t0 - t1); newTrack->Flush(); wt->Clear(t1, t0); wt->Paste(t1, newTrack); delete newTrack; } newRecordingTracks.Add(wt); } } t1 = 1000000000.0; // record for a long, long time (tens of years) } else { recordingChannels = gPrefs->Read(wxT("/AudioIO/RecordChannels"), 2); for (int c = 0; c < recordingChannels; c++) { WaveTrack *newTrack = p->GetTrackFactory()->NewWaveTrack(); int initialheight = newTrack->GetHeight(); newTrack->SetOffset(t0); if (recordingChannels <= 2) { newTrack->SetHeight(initialheight/recordingChannels); } else { newTrack->SetMinimized(true); } if (recordingChannels == 2) { if (c == 0) { newTrack->SetChannel(Track::LeftChannel); newTrack->SetLinked(true); } else { newTrack->SetChannel(Track::RightChannel); newTrack->SetTeamed(true); } } else { newTrack->SetChannel( Track::MonoChannel ); } newRecordingTracks.Add(newTrack); } // msmeyer: StartStream calls a callback which triggers auto-save, so // we add the tracks where recording is done into now. We remove them // later if starting the stream fails for (unsigned int i = 0; i < newRecordingTracks.GetCount(); i++) t->Add(newRecordingTracks[i]); } int token = gAudioIO->StartStream(playbackTracks, newRecordingTracks, /* REQUIRES PORTMIDI */ // midiTracks, t->GetTimeTrack(), p->GetRate(), t0, t1, p); bool success = (token != 0); if (success) { p->SetAudioIOToken(token); mBusyProject = p; SetVUMeters(p); } else { // msmeyer: Delete recently added tracks if opening stream fails if (!shifted) { for (unsigned int i = 0; i < newRecordingTracks.GetCount(); i++) { t->Remove(newRecordingTracks[i]); delete newRecordingTracks[i]; } } // msmeyer: Show error message if stream could not be opened wxMessageBox(_("Error while opening sound device. " wxT("Please check the input device settings and the project sample rate.")), _("Error"), wxOK | wxICON_EXCLAMATION, this); SetPlay(false); SetStop(false); SetRecord(false); } } }