int TrackList::GetNumExportChannels(bool selectionOnly) { /* counters for tracks panned different places */ int numLeft = 0; int numRight = 0; int numMono = 0; /* track iteration kit */ Track *tr; TrackListIterator iter; for (tr = iter.First(this); tr != NULL; tr = iter.Next()) { // Want only unmuted wave tracks. if ((tr->GetKind() != Track::Wave) || tr->GetMute()) continue; // do we only want selected ones? if (selectionOnly && !(tr->GetSelected())) { //want selected but this one is not continue; } // Found a left channel if (tr->GetChannel() == Track::LeftChannel) { numLeft++; } // Found a right channel else if (tr->GetChannel() == Track::RightChannel) { numRight++; } // Found a mono channel, but it may be panned else if (tr->GetChannel() == Track::MonoChannel) { float pan = ((WaveTrack*)tr)->GetPan(); // Figure out what kind of channel it should be if (pan == -1.0) { // panned hard left numLeft++; } else if (pan == 1.0) { // panned hard right numRight++; } else if (pan == 0) { // panned dead center numMono++; } else { // panned somewhere else numLeft++; numRight++; } } } // if there is stereo content, report 2, else report 1 if (numRight > 0 || numLeft > 0) { return 2; } return 1; }
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; }
bool MixAndRender(TrackList *tracks, TrackFactory *trackFactory, double rate, sampleFormat format, double startTime, double endTime, WaveTrack **newLeft, WaveTrack **newRight) { // This function was formerly known as "Quick Mix". It takes one or // more tracks as input; of all tracks that are selected, it mixes // them together, applying any envelopes, amplitude gain, panning, // and real-time effects in the process. The resulting pair of // tracks (stereo) are "rendered" and have no effects, gain, panning, // or envelopes. WaveTrack **waveArray; Track *t; int numWaves = 0; int numMono = 0; bool mono = false; int w; TrackListIterator iter(tracks); 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; double totalTime = 0.0; waveArray = new WaveTrack *[numWaves]; w = 0; t = iter.First(); while (t) { if (t->GetSelected() && t->GetKind() == Track::Wave) { waveArray[w++] = (WaveTrack *) t; if (t->GetEndTime() > totalTime) totalTime = t->GetEndTime(); } t = iter.Next(); } WaveTrack *mixLeft = trackFactory->NewWaveTrack(format, rate); mixLeft->SetName(_("Mix")); WaveTrack *mixRight = 0; if (mono) { mixLeft->SetChannel(Track::MonoChannel); } else { mixRight = trackFactory->NewWaveTrack(format, rate); mixRight->SetName(_("Mix")); mixLeft->SetChannel(Track::LeftChannel); mixRight->SetChannel(Track::RightChannel); mixLeft->SetLinked(true); mixRight->SetTeamed(true); } int maxBlockLen = mixLeft->GetIdealBlockSize(); if (startTime == endTime) { startTime = 0.0; endTime = totalTime; } Mixer *mixer = new Mixer(numWaves, waveArray, tracks->GetTimeTrack(), startTime, endTime, mono ? 1 : 2, maxBlockLen, false, rate, format); wxYield(); GetActiveProject()->ProgressShow(_NoAcc("&Mix and Render"), _("Mixing and rendering tracks")); wxBusyCursor busy; bool cancelling = false; while(!cancelling) { 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); } int progressvalue = int (1000 * (mixer->MixGetCurrentTime() / totalTime)); cancelling = !GetActiveProject()->ProgressUpdate(progressvalue); } GetActiveProject()->ProgressHide(); mixLeft->Flush(); *newLeft = mixLeft; if (!mono) { mixRight->Flush(); *newRight = 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 delete[] waveArray; delete mixer; return true; }
ExportMixerDialog::ExportMixerDialog( TrackList *tracks, bool selectionOnly, int maxNumChannels, wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &position, const wxSize& size, long style ) : wxDialog( parent, id, title, position, size, style | wxRESIZE_BORDER ) { int numTracks = 0; TrackListIterator iter( tracks ); for( Track *t = iter.First(); t; t = iter.Next() ) if( t->GetKind() == Track::Wave && ( t->GetSelected() || !selectionOnly ) ) { numTracks++; if( t->GetChannel() == Track::LeftChannel ) { mTrackNames.Add( t->GetName() + _( " - Left" ) ); mTrackNames.Add( t->GetName() + _( " - Right" ) ); t = iter.Next(); numTracks++; } else mTrackNames.Add( t->GetName() ); } mMixerSpec = new MixerSpec( numTracks, maxNumChannels ); wxBoxSizer *vertSizer = new wxBoxSizer( wxVERTICAL ); wxWindow *mixerPanel = new ExportMixerPanel( mMixerSpec, mTrackNames, this, ID_MIXERPANEL, wxDefaultPosition, wxSize( 400, -1 ) ); vertSizer->Add( mixerPanel, 1, wxEXPAND | wxALIGN_CENTRE | wxALL, 5 ); wxBoxSizer *horSizer = new wxBoxSizer( wxHORIZONTAL ); mChannelsText = new wxStaticText( this, -1, wxString::Format( _( "Output Channels: %2d" ), mMixerSpec->GetNumChannels() ) ); horSizer->Add( mChannelsText, 0, wxALIGN_LEFT | wxALL, 5 ); wxSlider *channels = new wxSlider( this, ID_SLIDER_CHANNEL, mMixerSpec->GetNumChannels(), 1, mMixerSpec->GetMaxNumChannels(), wxDefaultPosition, wxSize( 300, -1 ) ); horSizer->Add( channels, 0, wxEXPAND | wxALL, 5 ); vertSizer->Add( horSizer, 0, wxALIGN_CENTRE | wxALL, 5 ); horSizer = new wxBoxSizer( wxHORIZONTAL ); wxButton *cancel = new wxButton( this, wxID_CANCEL, _( "&Cancel" ) ); horSizer->Add( cancel, 0, wxALIGN_CENTRE | wxALL, 5 ); wxButton *ok = new wxButton( this, wxID_OK, _( "&OK" ) ); ok->SetDefault(); horSizer->Add( ok, 0, wxALIGN_CENTRE | wxALL, 5 ); vertSizer->Add( horSizer, 0, wxALIGN_CENTRE | wxALL, 5 ); SetAutoLayout( true ); SetSizer( vertSizer ); vertSizer->Fit( this ); vertSizer->SetSizeHints( this ); SetSizeHints( 640, 480, 20000, 20000 ); SetSize( 640, 480 ); }
/* * This first function contains the code common to both * Export() and ExportLossy() * * For safety, if the file already exists it stores the filename * the user wants in actualName, and returns a temporary file name. * The calling function should rename the file when it's successfully * exported. */ wxString ExportCommon( AudacityProject *project, wxString format, wxString defaultExtension, bool selectionOnly, double *t0, double *t1, int *numChannels, wxString &actualName, int maxNumChannels, MixerSpec **mixerSpec ) { TrackList *tracks = project->GetTracks(); /* First analyze the selected audio, perform sanity checks, and provide * information as appropriate. */ /* Tally how many are right, left, mono, and make sure at least one track is selected (if selectionOnly==true) */ int numSelected = 0, numLeft = 0, numRight = 0, numMono = 0; float earliestBegin = *t1; float latestEnd = *t0; TrackListIterator iter1(tracks); Track *tr = iter1.First(); while (tr) { if (tr->GetKind() == Track::Wave) { if (tr->GetSelected() || !selectionOnly) { numSelected++; if (tr->GetChannel() == Track::LeftChannel) numLeft++; else if (tr->GetChannel() == Track::RightChannel) numRight++; else if (tr->GetChannel() == Track::MonoChannel) { // It's a mono channel, but it may be panned float pan = ((WaveTrack*)tr)->GetPan(); if (pan == -1.0) numLeft++; else if (pan == 1.0) numRight++; else if (pan == 0) numMono++; else { // Panned partially off-center. Mix as stereo. numLeft++; numRight++; } } if(tr->GetOffset() < earliestBegin) earliestBegin = tr->GetOffset(); if(tr->GetEndTime() > latestEnd) latestEnd = tr->GetEndTime(); } } tr = iter1.Next(); } if(*t0 < earliestBegin) *t0 = earliestBegin; if(*t1 > latestEnd) *t1 = latestEnd; if (numSelected == 0 && selectionOnly) { wxMessageBox(_("No tracks are selected! Use Ctrl-A (Select All)\nChoose Export... to export all tracks."), _("Unable to export"), wxOK | wxICON_INFORMATION); return wxT(""); } /* Detemine if exported file will be stereo or mono or multichannel, and if mixing will occur */ bool downMix = (gPrefs->Read( wxT("/FileFormats/ExportDownMix" ), true ) !=0) ? true:false ; int channels; if( downMix || !mixerSpec ) { if (numRight > 0 || numLeft > 0) channels = 2; else channels = 1; numRight += numMono; numLeft += numMono; if (numLeft > 1 || numRight > 1) if (channels == 2) { ShowWarningDialog(project, wxT("MixStereo"), _("Your tracks will be mixed down to two stereo channels in the exported file.")); } else { ShowWarningDialog(project, wxT("MixMono"), _("Your tracks will be mixed down to a single mono channel in the exported file.")); } } else { ExportMixerDialog md( tracks, selectionOnly, maxNumChannels, NULL, 1, _( "Advanced Mixing Options" ) ); if( md.ShowModal() != wxID_OK ) return wxT( "" ); *mixerSpec = new MixerSpec( *( md.GetMixerSpec() ) ); channels = ( *mixerSpec )->GetNumChannels(); } /* Prepare and display the filename selection dialog */ wxString path = gPrefs->Read(wxT("/DefaultExportPath"), ::wxGetCwd()); wxString nameOnly; wxString extension; wxString defaultName = project->GetName(); wxString fName; wxString maskString; wxString endOfPathSep; #if 0 // this code shouldn't be here --dmazzoni //MERGE exercise exception if (defaultName == wxT("ThrowExceptionOnExport")) { //lda throw("Exercise exception"); } #endif if (defaultExtension.Left(1) == wxT(".")) defaultExtension = defaultExtension.Right(defaultExtension.Length()-1); maskString.Printf(wxT("%s files (*.%s)|*.%s|All files (*.*)|*.*"), format.c_str(), defaultExtension.c_str(), defaultExtension.c_str()); bool fileOkay; do { fileOkay = true; fName = defaultName + wxT(".") + defaultExtension; fName = wxFileSelector(wxString::Format(_("Save %s File As:"), format.c_str()), path, fName, // default file name defaultExtension, maskString, wxSAVE | wxOVERWRITE_PROMPT); if (fName.Length() >= 256) { wxMessageBox (_("Sorry, pathnames longer than 256 characters not supported.")); return wxT(""); } if (fName == wxT("")) return wxT(""); ::wxSplitPath(fName, &path, &nameOnly, &extension); // // Make sure the user doesn't accidentally save the file // as an extension with no name, like just plain ".wav". // if ((nameOnly.Left(1)==wxT(".") && extension==wxT("")) || (nameOnly==wxT("") && extension!=wxT(""))) { wxString prompt = _("Are you sure you want to save the file as \"")+ ::wxFileNameFromPath(fName)+wxT("\"?\n"); int action = wxMessageBox(prompt, wxT("Warning"), wxYES_NO | wxICON_EXCLAMATION, project); fileOkay = (action == wxYES); continue; } // // Check the extension - add the default if it's not there, // and warn user if it's abnormal. // wxString defaultExtension3 = defaultExtension; if (defaultExtension.Length() > 3) defaultExtension = defaultExtension.Left(3); if (extension == wxT("")) { #ifdef __WXMSW__ // Windows prefers 3-char uppercase extensions extension = defaultExtension; #else // Linux and Mac prefer lowercase extensions extension = defaultExtension.Lower(); #endif } else if (extension.Upper() != defaultExtension.Upper() && extension.Upper() != defaultExtension3.Upper()) { #ifdef __WXMSW__ // Windows prefers 3-char extensions defaultExtension3 = defaultExtension3; #endif wxString prompt; prompt.Printf(_("You are about to save a %s file with the name %s.\nNormally these files end in %s, and some programs will not open files with nonstandard extensions.\nAre you sure you want to save the file under this name?"), format.c_str(), (wxT("\"")+nameOnly+wxT(".")+extension+wxT("\"")).c_str(), (wxT("\".")+defaultExtension+wxT("\"")).c_str()); int action = wxMessageBox(prompt, wxT("Warning"), wxYES_NO | wxICON_EXCLAMATION, project); if (action == wxYES) fileOkay = true; else { fileOkay = false; defaultName = nameOnly + wxT(".") + extension; } } if (path.Length() > 0 && path.Last() == wxFILE_SEP_PATH) endOfPathSep = wxT(""); else endOfPathSep = wxFILE_SEP_PATH; fName = path + endOfPathSep + nameOnly + wxT(".") + extension; } while(!fileOkay); /* * Ensure that exporting a file by this name doesn't overwrite * one of the existing files in the project. (If it would * overwrite an existing file, DirManager tries to rename the * existing file.) */ if (!project->GetDirManager()->EnsureSafeFilename(wxFileName(fName))) return wxT(""); gPrefs->Write(wxT("/DefaultExportPath"), path); *numChannels = channels; /* * To be even MORE safe, return a temporary file name based * on this one... */ actualName = fName; int suffix = 0; while(::wxFileExists(fName)) { fName = path + endOfPathSep + nameOnly + wxString::Format(wxT("%d"), suffix) + wxT(".") + extension; suffix++; } return fName; }
bool ExportOGG(AudacityProject *project, bool stereo, wxString fName, bool selectionOnly, double t0, double t1) { double rate = project->GetRate(); wxWindow *parent = project; TrackList *tracks = project->GetTracks(); double quality = (gPrefs->Read("/FileFormats/OggExportQuality", 50)/(float)100.0); wxLogNull logNo; // temporarily disable wxWindows error messages bool cancelling = false; wxFFile outFile(fName, "wb"); if(!outFile.IsOpened()) { wxMessageBox(_("Unable to open target file for writing")); return false; } // All the Ogg and Vorbis encoding data ogg_stream_state stream; ogg_page page; ogg_packet packet; vorbis_info info; vorbis_comment comment; vorbis_dsp_state dsp; vorbis_block block; // Encoding setup vorbis_info_init(&info); vorbis_encode_init_vbr(&info, stereo ? 2 : 1, int(rate + 0.5), quality); vorbis_comment_init(&comment); // If we wanted to add comments, we would do it here // Set up analysis state and auxiliary encoding storage vorbis_analysis_init(&dsp, &info); vorbis_block_init(&dsp, &block); // Set up packet->stream encoder. According to encoder example, // a random serial number makes it more likely that you can make // chained streams with concatenation. srand(time(NULL)); ogg_stream_init(&stream, rand()); // First we need to write the required headers: // 1. The Ogg bitstream header, which contains codec setup params // 2. The Vorbis comment header // 3. The bitstream codebook. // // After we create those our responsibility is complete, libvorbis will // take care of any other ogg bistream constraints (again, according // to the example encoder source) ogg_packet bitstream_header; ogg_packet comment_header; ogg_packet codebook_header; vorbis_analysis_headerout(&dsp, &comment, &bitstream_header, &comment_header, &codebook_header); // Place these headers into the stream ogg_stream_packetin(&stream, &bitstream_header); ogg_stream_packetin(&stream, &comment_header); ogg_stream_packetin(&stream, &codebook_header); // Flushing these headers now guarentees that audio data will // start on a new page, which apparently makes streaming easier ogg_stream_flush(&stream, &page); outFile.Write(page.header, page.header_len); outFile.Write(page.body, page.body_len); double t = t0; bool done = false; wxProgressDialog *progress = NULL; wxYield(); wxStartTimer(); while(!done && !cancelling){ float deltat = (float)SAMPLES_PER_RUN / rate; sampleCount samplesThisRun = SAMPLES_PER_RUN; Mixer *mixer = new Mixer(stereo ? 2 : 1, SAMPLES_PER_RUN, /* interleaved = */ false, rate, floatSample); if(t + deltat > t1) { done = true; deltat = t1 - t; samplesThisRun = int(deltat * rate + 0.5); } mixer->Clear(); TrackListIterator iter(tracks); Track *tr = iter.First(); while (tr) { if (tr->GetKind() == Track::Wave) { if (tr->GetSelected() || !selectionOnly) { if (tr->GetChannel() == Track::MonoChannel) mixer->MixMono((WaveTrack *) tr, t, t + deltat); else if (tr->GetChannel() == Track::LeftChannel) mixer->MixLeft((WaveTrack *) tr, t, t + deltat); else if (tr->GetChannel() == Track::RightChannel) mixer->MixRight((WaveTrack *) tr, t, t + deltat); } } tr = iter.Next(); } float **vorbis_buffer = vorbis_analysis_buffer(&dsp, SAMPLES_PER_RUN); float *left = (float *)mixer->GetBuffer(0); memcpy(vorbis_buffer[0], left, sizeof(float)*SAMPLES_PER_RUN); if(stereo) { float *right = (float *)mixer->GetBuffer(1); memcpy(vorbis_buffer[1], right, sizeof(float)*SAMPLES_PER_RUN); } // tell the encoder how many samples we have vorbis_analysis_wrote(&dsp, samplesThisRun); // I don't understand what this call does, so here is the comment // from the example, verbatim: // // vorbis does some data preanalysis, then divvies up blocks // for more involved (potentially parallel) processing. Get // a single block for encoding now while(vorbis_analysis_blockout(&dsp, &block) == 1) { // analysis, assume we want to use bitrate management vorbis_analysis(&block, NULL); vorbis_bitrate_addblock(&block); while(vorbis_bitrate_flushpacket(&dsp, &packet)) { // add the packet to the bitstream ogg_stream_packetin(&stream, &packet); int result = ogg_stream_pageout(&stream, &page); if(result != 0) { outFile.Write(page.header, page.header_len); outFile.Write(page.body, page.body_len); } } } if(progress) cancelling = !progress->Update(int (((t - t0) * 1000) / (t1 - t0) + 0.5)); else if(wxGetElapsedTime(false) > 500) { wxString message = selectionOnly ? _("Exporting the selected audio as Ogg Vorbis") : _("Exporting the entire project as Ogg Vorbis"); progress = new wxProgressDialog( _("Export"), message, 1000, parent, wxPD_CAN_ABORT | wxPD_REMAINING_TIME | wxPD_AUTO_HIDE); } delete mixer; t += deltat; } outFile.Close(); if(progress) delete progress; return true; }
/* * This first function contains the code common to both * Export() and ExportLossy() * * For safety, if the file already exists it stores the filename * the user wants in actualName, and returns a temporary file name. * The calling function should rename the file when it's successfully * exported. */ wxString ExportCommon(AudacityProject *project, wxString format, wxString defaultExtension, bool selectionOnly, double *t0, double *t1, bool *isStereo, wxString &actualName) { TrackList *tracks = project->GetTracks(); /* First analyze the selected audio, perform sanity checks, and provide * information as appropriate. */ /* Tally how many are right, left, mono, and make sure at least one track is selected (if selectionOnly==true) */ int numSelected = 0, numLeft = 0, numRight = 0, numMono = 0; float earliestBegin = *t1; float latestEnd = *t0; TrackListIterator iter1(tracks); Track *tr = iter1.First(); while (tr) { if (tr->GetKind() == Track::Wave) { if (tr->GetSelected() || !selectionOnly) { numSelected++; if (tr->GetChannel() == Track::LeftChannel) numLeft++; else if (tr->GetChannel() == Track::RightChannel) numRight++; else if (tr->GetChannel() == Track::MonoChannel) { // It's a mono channel, but it may be panned float pan = ((WaveTrack*)tr)->GetPan(); if (pan == -1.0) numLeft++; else if (pan == 1.0) numRight++; else if (pan == 0) numMono++; else { numLeft++; numRight++; } } if(tr->GetOffset() < earliestBegin) earliestBegin = tr->GetOffset(); if(tr->GetEndTime() > latestEnd) latestEnd = tr->GetEndTime(); } } tr = iter1.Next(); } if(*t0 < earliestBegin) *t0 = earliestBegin; if(*t1 > latestEnd) *t1 = latestEnd; if (numSelected == 0 && selectionOnly) { wxMessageBox(_("No tracks are selected!\n" "Choose Export... to export all tracks.")); return ""; } /* Detemine if exported file will be stereo or mono, and if mixing will occur */ bool stereo = false; if (numRight > 0 || numLeft > 0) stereo = true; numRight += numMono; numLeft += numMono; if (numLeft > 1 || numRight > 1) if (stereo) { ShowWarningDialog(project, "MixStereo", _("Your tracks will be mixed down to two " "stereo channels in the exported file.")); } else { ShowWarningDialog(project, "MixMono", _("Your tracks will be mixed down to a " "single mono channel in the exported file.")); } /* Prepare and display the filename selection dialog */ wxString path = gPrefs->Read("/DefaultExportPath", FROMFILENAME(::wxGetCwd())); wxString nameOnly; wxString extension; wxString defaultName = project->GetName(); wxString fName; wxString maskString; wxString endOfPathSep; if (defaultExtension.Left(1) == ".") defaultExtension = defaultExtension.Right(defaultExtension.Length()-1); maskString.Printf("%s files (*.%s)|*.%s|All files (*.*)|*.*", (const char *)format, (const char *)defaultExtension, (const char *)defaultExtension); bool fileOkay; do { fileOkay = true; fName = defaultName + "." + defaultExtension; fName = wxFileSelector(wxString::Format(_("Save %s File As:"), (const char *) format), path, fName, // default file name defaultExtension, maskString, wxSAVE | wxOVERWRITE_PROMPT); if (fName.Length() >= 256) { wxMessageBox (_("Sorry, pathnames longer than 256 characters not supported.")); return ""; } if (fName == "") return ""; ::wxSplitPath(fName, &path, &nameOnly, &extension); // // Make sure the user doesn't accidentally save the file // as an extension with no name, like just plain ".wav". // if ((nameOnly.Left(1)=="." && extension=="") || (nameOnly=="" && extension!="")) { wxString prompt = _("Are you sure you want to save the file as \"")+ ::wxFileNameFromPath(fName)+"\"?\n"; int action = wxMessageBox(prompt, "Warning", wxYES_NO | wxICON_EXCLAMATION, project); fileOkay = (action == wxYES); continue; } // // Check the extension - add the default if it's not there, // and warn user if it's abnormal. // wxString defaultExtension3 = defaultExtension; if (defaultExtension.Length() > 3) defaultExtension = defaultExtension.Left(3); if (extension == "") { #ifdef __WXMSW__ // Windows prefers 3-char uppercase extensions extension = defaultExtension; #else // Linux and Mac prefer lowercase extensions extension = defaultExtension.Lower(); #endif } else if (extension.Upper() != defaultExtension.Upper() && extension.Upper() != defaultExtension3.Upper()) { #ifdef __WXMSW__ // Windows prefers 3-char extensions defaultExtension3 = defaultExtension3; #endif wxString prompt; prompt.Printf(_("You are about to save a %s file with the name %s.\n" "Normally these files end in %s, and some programs " "will not open files with nonstandard extensions.\n" "Are you sure you want to save the file " "under this name?"), (const char *)format, (const char *)("\""+nameOnly+"."+extension+"\""), (const char *)("\"."+defaultExtension+"\"")); int action = wxMessageBox(prompt, "Warning", wxYES_NO | wxICON_EXCLAMATION, project); if (action == wxYES) fileOkay = true; else { fileOkay = false; defaultName = nameOnly + "." + extension; } } if (path.Length() > 0 && path.Last() == wxFILE_SEP_PATH) endOfPathSep = ""; else endOfPathSep = wxFILE_SEP_PATH; fName = path + endOfPathSep + nameOnly + "." + extension; } while(!fileOkay); /* * Ensure that exporting a file by this name doesn't overwrite * one of the existing files in the project. (If it would * overwrite an existing file, DirManager tries to rename the * existing file.) */ if (!project->GetDirManager()->EnsureSafeFilename(wxFileName(fName))) return ""; gPrefs->Write("/DefaultExportPath", path); *isStereo = stereo; /* * To be even MORE safe, return a temporary file name based * on this one... */ actualName = fName; int suffix = 0; while(::wxFileExists(FILENAME(fName))) { fName = path + endOfPathSep + nameOnly + wxString::Format("%d", suffix) + "." + extension; suffix++; } return fName; }
//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 } }
bool ExportMultiple::ExportMultipleByLabel(bool byName, wxString prefix) { Track *tr; int numFiles = mNumLabels; int l = 0; int numLeft = 0; int numRight = 0; int numMono = 0; // Account for exporting before first label if (mFirst->GetValue()) { l--; numFiles++; } // Figure out if we're exporting stereo or mono for (tr = mIterator.First(mTracks); tr != NULL; tr = mIterator.Next()) { // Only want wave tracks if (!tr->GetKind() == Track::Wave) { continue; } // Found a left channel if (tr->GetChannel() == Track::LeftChannel) { numLeft++; } // Found a right channel else if (tr->GetChannel() == Track::RightChannel) { numRight++; } // Found a mono channel, but it may be panned else if (tr->GetChannel() == Track::MonoChannel) { float pan = ((WaveTrack*)tr)->GetPan(); // Figure out what kind of channel it should be if (pan == -1.0) { numLeft++; } else if (pan == 1.0) { numRight++; } else if (pan == 0) { numMono++; } else { numLeft++; numRight++; } } } // We we have a stereo or mono mix? bool stereo = false; if (numRight > 0 || numLeft > 0) { stereo = true; } wxArrayString otherNames; wxString name; double t0, t1; int count = 0; bool ok = true; // Examine all labels while (l < mNumLabels) { const LabelStruct *info = NULL; // Get file name and starting time if (l < 0) { name = mFirstFileName->GetValue(); t0 = 0.0; } else { info = mLabels->GetLabel(l); name = info->title; t0 = info->t; } // Figure out the ending time if (info && info->t < info->t1) { t1 = info->t1; } else if (l < mNumLabels-1) { const LabelStruct *info1 = mLabels->GetLabel(l+1); t1 = info1->t; } else { t1 = mTracks->GetEndTime(); } // Numbering files... if (!byName) { name.Printf(wxT("%s-%d"), prefix.c_str(), count+1); } // Make sure the file name is unique within the directory MakeNameUnique(otherNames, name); // Export it ok = DoExport(stereo, name, false, t0, t1, count+1); if (!ok) { break; } count++; l++; } // Give 'em the result ::wxMessageBox(wxString::Format(ok ? _("Successfully exported %d file(s).") : _("Something went wrong after exporting %d file(s)."), count), _("Export Multiple"), wxOK | wxCENTRE, this); return ok; }
bool ExportCL(AudacityProject *project, bool stereo, wxString fName, bool selectionOnly, double t0, double t1) { int rate = int(project->GetRate() + 0.5); wxWindow *parent = project; TrackList *tracks = project->GetTracks(); wxString command = gPrefs->Read("/FileFormats/ExternalProgramExportCommand", "lame - '%f'"); command.Replace("%f", fName); /* establish parameters */ int channels = stereo ? 2 : 1; unsigned long totalSamples = (unsigned long)((t1 - t0) * rate + 0.5); unsigned long sampleBytes = totalSamples * channels * SAMPLE_SIZE(int16Sample); double timeStep = 10.0; // write in blocks of 10 secs /* fill up the wav header */ wav_header header; header.riffID[0] = 'R'; header.riffID[1] = 'I'; header.riffID[2] = 'F'; header.riffID[3] = 'F'; header.riffType[0] = 'W'; header.riffType[1] = 'A'; header.riffType[2] = 'V'; header.riffType[3] = 'E'; header.lenAfterRiff = sampleBytes + 32; header.fmtID[0] = 'f'; header.fmtID[1] = 'm'; header.fmtID[2] = 't'; header.fmtID[3] = ' '; header.formatChunkLen = 16; header.formatTag = 1; header.channels = channels; header.sampleRate = rate; header.bitsPerSample = SAMPLE_SIZE(int16Sample) * 8; header.blockAlign = header.bitsPerSample * header.channels; header.avgBytesPerSec = header.sampleRate * header.blockAlign; header.dataID[0] = 'd'; header.dataID[1] = 'a'; header.dataID[2] = 't'; header.dataID[3] = 'a'; header.dataLen = sampleBytes; FILE *pipe = popen(command.c_str(), "w"); /* write the header */ fwrite( &header, sizeof(wav_header), 1, pipe ); //sampleCount maxSamples = int (timeStep * rate + 0.5); wxProgressDialog *progress = NULL; wxYield(); wxStartTimer(); wxBusyCursor busy; bool cancelling = false; double t = t0; while (t < t1 && !cancelling) { double deltat = timeStep; if (t + deltat > t1) deltat = t1 - t; sampleCount numSamples = int (deltat * rate + 0.5); Mixer *mixer = new Mixer(channels, numSamples, true, rate, int16Sample); wxASSERT(mixer); mixer->Clear(); char *buffer = new char[numSamples * SAMPLE_SIZE(int16Sample) * channels]; wxASSERT(buffer); TrackListIterator iter(tracks); Track *tr = iter.First(); while (tr) { if (tr->GetKind() == Track::Wave) { if (tr->GetSelected() || !selectionOnly) { if (tr->GetChannel() == Track::MonoChannel) mixer->MixMono((WaveTrack *) tr, t, t + deltat); if (tr->GetChannel() == Track::LeftChannel) mixer->MixLeft((WaveTrack *) tr, t, t + deltat); if (tr->GetChannel() == Track::RightChannel) mixer->MixRight((WaveTrack *) tr, t, t + deltat); } } tr = iter.Next(); } samplePtr mixed = mixer->GetBuffer(); // Byte-swapping is neccesary on big-endian machines, since // WAV files are little-endian #if wxBYTE_ORDER == wxBIG_ENDIAN { short *buffer = (short*)mixed; for( int i = 0; i < numSamples; i++ ) buffer[i] = wxINT16_SWAP_ON_BE(buffer[i]); } #endif fwrite( mixed, numSamples * channels * SAMPLE_SIZE(int16Sample), 1, pipe ); t += deltat; if (!progress && wxGetElapsedTime(false) > 500) { wxString message; if (selectionOnly) message = "Exporting the selected audio using command-line encoder"; else message = "Exporting the entire project using command-line encoder"; progress = new wxProgressDialog("Export", message, 1000, parent, wxPD_CAN_ABORT | wxPD_REMAINING_TIME | wxPD_AUTO_HIDE); } if (progress) { cancelling = !progress->Update(int (((t - t0) * 1000) / (t1 - t0) + 0.5)); } delete mixer; delete[]buffer; } pclose( pipe ); if(progress) delete progress; return true; }
ExportMixerDialog::ExportMixerDialog( TrackList *tracks, bool selectedOnly, int maxNumChannels, wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &position, const wxSize& size, long style ) : wxDialog( parent, id, title, position, size, style | wxRESIZE_BORDER ) { int numTracks = 0; TrackListIterator iter( tracks ); for( Track *t = iter.First(); t; t = iter.Next() ) { if( t->GetKind() == Track::Wave && ( t->GetSelected() || !selectedOnly ) ) { numTracks++; if( t->GetChannel() == Track::LeftChannel ) { mTrackNames.Add( t->GetName() + _( " - Left" ) ); mTrackNames.Add( t->GetName() + _( " - Right" ) ); t = iter.Next(); numTracks++; } else mTrackNames.Add( t->GetName() ); } } // JKC: This is an attempt to fix a 'watching brief' issue, where the slider is // sometimes not slidable. My suspicion is that a mixer may incorrectly // state the number of channels - so we assume there are always at least two. // The downside is that if someone is exporting to a mono device, the dialog // will allow them to output to two channels. Hmm. We may need to revisit this. if (maxNumChannels < 2 ) maxNumChannels = 2; if (maxNumChannels > 32) maxNumChannels = 32; mMixerSpec = new MixerSpec( numTracks, maxNumChannels ); wxBoxSizer *vertSizer = new wxBoxSizer( wxVERTICAL ); wxWindow *mixerPanel = new ExportMixerPanel( mMixerSpec, mTrackNames, this, ID_MIXERPANEL, wxDefaultPosition, wxSize( 400, -1 ) ); vertSizer->Add( mixerPanel, 1, wxEXPAND | wxALIGN_CENTRE | wxALL, 5 ); wxBoxSizer *horSizer = new wxBoxSizer( wxHORIZONTAL ); mChannelsText = new wxStaticText( this, -1, wxString::Format( _( "Output Channels: %2d" ), mMixerSpec->GetNumChannels() ) ); horSizer->Add( mChannelsText, 0, wxALIGN_LEFT | wxALL, 5 ); wxSlider *channels = new wxSlider( this, ID_SLIDER_CHANNEL, mMixerSpec->GetNumChannels(), 1, mMixerSpec->GetMaxNumChannels(), wxDefaultPosition, wxSize( 300, -1 ) ); horSizer->Add( channels, 0, wxEXPAND | wxALL, 5 ); vertSizer->Add( horSizer, 0, wxALIGN_CENTRE | wxALL, 5 ); vertSizer->Add( CreateStdButtonSizer(this, eCancelButton|eOkButton), 0, wxEXPAND ); SetAutoLayout( true ); SetSizer( vertSizer ); vertSizer->Fit( this ); vertSizer->SetSizeHints( this ); SetSizeHints( 640, 480, 20000, 20000 ); SetSize( 640, 480 ); }
bool Exporter::ExamineTracks() { // Init mNumSelected = 0; mNumLeft = 0; mNumRight = 0; mNumMono = 0; // First analyze the selected audio, perform sanity checks, and provide // information as appropriate. // Tally how many are right, left, mono, and make sure at // least one track is selected (if selectedOnly==true) float earliestBegin = mT1; float latestEnd = mT0; TrackList *tracks = mProject->GetTracks(); TrackListIterator iter1(tracks); Track *tr = iter1.First(); while (tr) { if (tr->GetKind() == Track::Wave) { if (tr->GetSelected() || !mSelectedOnly) { mNumSelected++; if (tr->GetChannel() == Track::LeftChannel) { mNumLeft++; } else if (tr->GetChannel() == Track::RightChannel) { mNumRight++; } else if (tr->GetChannel() == Track::MonoChannel) { // It's a mono channel, but it may be panned float pan = ((WaveTrack*)tr)->GetPan(); if (pan == -1.0) mNumLeft++; else if (pan == 1.0) mNumRight++; else if (pan == 0) mNumMono++; else { // Panned partially off-center. Mix as stereo. mNumLeft++; mNumRight++; } } if (tr->GetOffset() < earliestBegin) { earliestBegin = tr->GetOffset(); } if (tr->GetEndTime() > latestEnd) { latestEnd = tr->GetEndTime(); } } } tr = iter1.Next(); } if (mSelectedOnly && mNumSelected == 0) { wxMessageBox(_("No tracks are selected! Use Ctrl-A (Select All)\nChoose Export... to export all tracks."), _("Unable to export"), wxOK | wxICON_INFORMATION); return false; } if (mT0 < earliestBegin) mT0 = earliestBegin; if (mT1 > latestEnd) mT1 = latestEnd; return true; }