bool Track::LinkConsistencyCheck() { // Sanity checks for linked tracks; unsetting the linked property // doesn't fix the problem, but it likely leaves us with orphaned // blockfiles instead of much worse problems. bool err = false; if (GetLinked()) { Track *l = GetLink(); if (l) { // A linked track's partner should never itself be linked if (l->GetLinked()) { wxLogWarning( wxT("Left track %s had linked right track %s with extra right track link.\n Removing extra link from right track."), GetName(), l->GetName()); err = true; l->SetLinked(false); } // Channels should be left and right if ( !( (GetChannel() == Track::LeftChannel && l->GetChannel() == Track::RightChannel) || (GetChannel() == Track::RightChannel && l->GetChannel() == Track::LeftChannel) ) ) { wxLogWarning( wxT("Track %s and %s had left/right track links out of order. Setting tracks to not be linked."), GetName(), l->GetName()); err = true; SetLinked(false); } } else { wxLogWarning( wxT("Track %s had link to NULL track. Setting it to not be linked."), GetName()); err = true; SetLinked(false); } } return ! err; }
void TrackList::GroupChannels( Track &track, size_t groupSize, bool resetChannels ) { // If group size is more than two, for now only the first two channels // are grouped as stereo, and any others remain mono auto list = track.mList.lock(); if ( groupSize > 0 && list.get() == this ) { auto iter = track.mNode.first; auto after = iter; auto end = this->ListOfTracks::end(); auto count = groupSize; for ( ; after != end && count; ++after, --count ) ; if ( count == 0 ) { auto unlink = [&] ( Track &tr ) { if ( tr.GetLinked() ) { if ( resetChannels ) { auto link = tr.GetLink(); if ( link ) link->SetChannel( Track::MonoChannel ); } tr.SetLinked( false ); } if ( resetChannels ) tr.SetChannel( Track::MonoChannel ); }; // Disassociate previous tracks -- at most one auto pLeader = this->FindLeader( &track ); if ( *pLeader && *pLeader != &track ) unlink( **pLeader ); // First disassociate given and later tracks, then reassociate them for ( auto iter2 = iter; iter2 != after; ++iter2 ) unlink( **iter2 ); if ( groupSize > 1 ) { const auto channel = *iter++; channel->SetLinked( true ); channel->SetChannel( Track::LeftChannel ); (*iter++)->SetChannel( Track::RightChannel ); while (iter != after) (*iter++)->SetChannel( Track::MonoChannel ); } return; } } // *this does not contain the track or sufficient following channels // or group size is zero THROW_INCONSISTENCY_EXCEPTION; }
void Track::SetLinked(bool l) { auto pList = mList.lock(); if (pList && !pList->mPendingUpdates.empty()) { auto orig = pList->FindById( GetId() ); if (orig && orig != this) { // delegate, and rely on RecalcPositions to copy back orig->SetLinked(l); return; } } DoSetLinked(l); if (pList) { pList->RecalcPositions(mNode); pList->ResizingEvent(mNode); } }
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()); }
//TODO-MB: wouldn't it make more sense to DELETE the time track after 'mix and render'? void MixAndRender(TrackList *tracks, TrackFactory *trackFactory, double rate, sampleFormat format, double startTime, double endTime, WaveTrack::Holder &uLeft, WaveTrack::Holder &uRight) { uLeft.reset(), uRight.reset(); // This function was formerly known as "Quick Mix". Track *t; bool mono = false; /* flag if output can be mono without loosing anything*/ bool oneinput = false; /* flag set to true if there is only one input track (mono or stereo) */ TrackListIterator iter(tracks); SelectedTrackListOfKindIterator usefulIter(Track::Wave, tracks); // this only iterates tracks which are relevant to this function, i.e. // selected WaveTracks. The tracklist is (confusingly) the list of all // tracks in the project int numWaves = 0; /* number of wave tracks in the selection */ int numMono = 0; /* number of mono, centre-panned wave tracks in selection*/ t = iter.First(); while (t) { if (t->GetSelected() && t->GetKind() == Track::Wave) { numWaves++; float pan = ((WaveTrack*)t)->GetPan(); if (t->GetChannel() == Track::MonoChannel && pan == 0) numMono++; } t = iter.Next(); } if (numMono == numWaves) mono = true; /* the next loop will do two things at once: * 1. build an array of all the wave tracks were are trying to process * 2. determine when the set of WaveTracks starts and ends, in case we * need to work out for ourselves when to start and stop rendering. */ double mixStartTime = 0.0; /* start time of first track to start */ bool gotstart = false; // flag indicates we have found a start time double mixEndTime = 0.0; /* end time of last track to end */ double tstart, tend; // start and end times for one track. WaveTrackConstArray waveArray; t = iter.First(); while (t) { if (t->GetSelected() && t->GetKind() == Track::Wave) { waveArray.push_back(static_cast<WaveTrack *>(t)); tstart = t->GetStartTime(); tend = t->GetEndTime(); if (tend > mixEndTime) mixEndTime = tend; // try and get the start time. If the track is empty we will get 0, // which is ambiguous because it could just mean the track starts at // the beginning of the project, as well as empty track. The give-away // is that an empty track also ends at zero. if (tstart != tend) { // we don't get empty tracks here if (!gotstart) { // no previous start, use this one unconditionally mixStartTime = tstart; gotstart = true; } else if (tstart < mixStartTime) mixStartTime = tstart; // have a start, only make it smaller } // end if start and end are different } // end if track is a selected WaveTrack. /** @TODO: could we not use a SelectedTrackListOfKindIterator here? */ t = iter.Next(); } /* create the destination track (NEW track) */ if ((numWaves == 1) || ((numWaves == 2) && (usefulIter.First()->GetLink() != NULL))) oneinput = true; // only one input track (either 1 mono or one linked stereo pair) auto mixLeft = trackFactory->NewWaveTrack(format, rate); if (oneinput) mixLeft->SetName(usefulIter.First()->GetName()); /* set name of output track to be the same as the sole input track */ else mixLeft->SetName(_("Mix")); mixLeft->SetOffset(mixStartTime); decltype(mixLeft) mixRight{}; if (mono) { mixLeft->SetChannel(Track::MonoChannel); } else { mixRight = trackFactory->NewWaveTrack(format, rate); if (oneinput) { if (usefulIter.First()->GetLink() != NULL) // we have linked track mixLeft->SetName(usefulIter.First()->GetLink()->GetName()); /* set name to match input track's right channel!*/ else mixLeft->SetName(usefulIter.First()->GetName()); /* set name to that of sole input channel */ } else mixRight->SetName(_("Mix")); mixLeft->SetChannel(Track::LeftChannel); mixRight->SetChannel(Track::RightChannel); mixRight->SetOffset(mixStartTime); mixLeft->SetLinked(true); } int maxBlockLen = mixLeft->GetIdealBlockSize(); // If the caller didn't specify a time range, use the whole range in which // any input track had clips in it. if (startTime == endTime) { startTime = mixStartTime; endTime = mixEndTime; } Mixer mixer(waveArray, Mixer::WarpOptions(tracks->GetTimeTrack()), startTime, endTime, mono ? 1 : 2, maxBlockLen, false, rate, format); ::wxSafeYield(); int updateResult = eProgressSuccess; { ProgressDialog progress(_("Mix and Render"), _("Mixing and rendering tracks")); while (updateResult == eProgressSuccess) { sampleCount blockLen = mixer.Process(maxBlockLen); if (blockLen == 0) break; if (mono) { samplePtr buffer = mixer.GetBuffer(); mixLeft->Append(buffer, format, blockLen); } else { samplePtr buffer; buffer = mixer.GetBuffer(0); mixLeft->Append(buffer, format, blockLen); buffer = mixer.GetBuffer(1); mixRight->Append(buffer, format, blockLen); } updateResult = progress.Update(mixer.MixGetCurrentTime() - startTime, endTime - startTime); } } mixLeft->Flush(); if (!mono) mixRight->Flush(); if (updateResult == eProgressCancelled || updateResult == eProgressFailed) { return; } else { uLeft = std::move(mixLeft), uRight = std::move(mixRight); #if 0 int elapsedMS = wxGetElapsedTime(); double elapsedTime = elapsedMS * 0.001; double maxTracks = totalTime / (elapsedTime / numWaves); // Note: these shouldn't be translated - they're for debugging // and profiling only. printf(" Tracks: %d\n", numWaves); printf(" Mix length: %f sec\n", totalTime); printf("Elapsed time: %f sec\n", elapsedTime); printf("Max number of tracks to mix in real time: %f\n", maxTracks); #endif } }