1365 lines
37 KiB
C++
1365 lines
37 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
Export.cpp
|
|
|
|
Dominic Mazzoni
|
|
|
|
*******************************************************************//**
|
|
|
|
\class Export
|
|
\brief Main class to control the export function.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class ExportType
|
|
\brief Container for information about supported export types.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class ExportMixerDialog
|
|
\brief Dialog for advanced mixing.
|
|
|
|
*//****************************************************************//**
|
|
|
|
\class ExportMixerPanel
|
|
\brief Panel that displays mixing for advanced mixing option.
|
|
|
|
*//********************************************************************/
|
|
|
|
#include "../Audacity.h"
|
|
#include "Export.h"
|
|
|
|
#include <wx/dynarray.h>
|
|
#include <wx/file.h>
|
|
#include <wx/filename.h>
|
|
#include <wx/msgdlg.h>
|
|
#include <wx/progdlg.h>
|
|
#include <wx/sizer.h>
|
|
#include <wx/slider.h>
|
|
#include <wx/statbox.h>
|
|
#include <wx/stattext.h>
|
|
#include <wx/string.h>
|
|
#include <wx/textctrl.h>
|
|
#include <wx/timer.h>
|
|
#include <wx/dcmemory.h>
|
|
#include <wx/window.h>
|
|
|
|
#include "ExportPCM.h"
|
|
#include "ExportMP3.h"
|
|
#include "ExportOGG.h"
|
|
#include "ExportFLAC.h"
|
|
#include "ExportCL.h"
|
|
#include "ExportMP2.h"
|
|
#include "ExportFFmpeg.h"
|
|
|
|
#include "sndfile.h"
|
|
|
|
#include "FileDialog.h"
|
|
|
|
#include "../DirManager.h"
|
|
#include "../FileFormats.h"
|
|
#include "../Internat.h"
|
|
#include "../Mix.h"
|
|
#include "../Prefs.h"
|
|
#include "../Project.h"
|
|
#include "../ShuttleGui.h"
|
|
#include "../WaveTrack.h"
|
|
#include "../widgets/Warning.h"
|
|
#include "../AColor.h"
|
|
#include "../Dependencies.h"
|
|
|
|
//----------------------------------------------------------------------------
|
|
// ExportPlugin
|
|
//----------------------------------------------------------------------------
|
|
#include <wx/arrimpl.cpp>
|
|
|
|
WX_DEFINE_USER_EXPORTED_OBJARRAY(FormatInfoArray);
|
|
|
|
ExportPlugin::ExportPlugin()
|
|
{
|
|
mFormatInfos.Empty();
|
|
}
|
|
|
|
ExportPlugin::~ExportPlugin()
|
|
{
|
|
mFormatInfos.Clear();
|
|
}
|
|
|
|
bool ExportPlugin::CheckFileName(wxFileName & WXUNUSED(filename), int WXUNUSED(format))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/** \brief Add a NEW entry to the list of formats this plug-in can export
|
|
*
|
|
* To configure the format use SetFormat, SetCanMetaData etc with the index of
|
|
* the format.
|
|
* @return The number of formats currently set up. This is one more than the
|
|
* index of the newly added format.
|
|
*/
|
|
int ExportPlugin::AddFormat()
|
|
{
|
|
FormatInfo nf;
|
|
mFormatInfos.Add(nf);
|
|
return mFormatInfos.Count();
|
|
}
|
|
|
|
int ExportPlugin::GetFormatCount()
|
|
{
|
|
return mFormatInfos.Count();
|
|
}
|
|
|
|
/**
|
|
* @param index The plugin to set the format for (range 0 to one less than the
|
|
* count of formats)
|
|
*/
|
|
void ExportPlugin::SetFormat(const wxString & format, int index)
|
|
{
|
|
mFormatInfos[index].mFormat = format;
|
|
}
|
|
|
|
void ExportPlugin::SetDescription(const wxString & description, int index)
|
|
{
|
|
mFormatInfos[index].mDescription = description;
|
|
}
|
|
|
|
void ExportPlugin::AddExtension(const wxString &extension,int index)
|
|
{
|
|
mFormatInfos[index].mExtensions.Add(extension);
|
|
}
|
|
|
|
void ExportPlugin::SetExtensions(const wxArrayString & extensions, int index)
|
|
{
|
|
mFormatInfos[index].mExtensions = extensions;
|
|
}
|
|
|
|
void ExportPlugin::SetMask(const wxString & mask, int index)
|
|
{
|
|
mFormatInfos[index].mMask = mask;
|
|
}
|
|
|
|
void ExportPlugin::SetMaxChannels(int maxchannels, int index)
|
|
{
|
|
mFormatInfos[index].mMaxChannels = maxchannels;
|
|
}
|
|
|
|
void ExportPlugin::SetCanMetaData(bool canmetadata, int index)
|
|
{
|
|
mFormatInfos[index].mCanMetaData = canmetadata;
|
|
}
|
|
|
|
wxString ExportPlugin::GetFormat(int index)
|
|
{
|
|
return mFormatInfos[index].mFormat;
|
|
}
|
|
|
|
wxString ExportPlugin::GetDescription(int index)
|
|
{
|
|
return mFormatInfos[index].mDescription;
|
|
}
|
|
|
|
wxString ExportPlugin::GetExtension(int index)
|
|
{
|
|
return mFormatInfos[index].mExtensions[0];
|
|
}
|
|
|
|
wxArrayString ExportPlugin::GetExtensions(int index)
|
|
{
|
|
return mFormatInfos[index].mExtensions;
|
|
}
|
|
|
|
wxString ExportPlugin::GetMask(int index)
|
|
{
|
|
if (!mFormatInfos[index].mMask.IsEmpty()) {
|
|
return mFormatInfos[index].mMask;
|
|
}
|
|
|
|
wxString mask = GetDescription(index) + wxT("|");
|
|
|
|
// Build the mask
|
|
// wxString ext = GetExtension(index);
|
|
wxArrayString exts = GetExtensions(index);
|
|
for (size_t i = 0; i < exts.GetCount(); i++) {
|
|
mask += wxT("*.") + exts[i] + wxT(";");
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
int ExportPlugin::GetMaxChannels(int index)
|
|
{
|
|
return mFormatInfos[index].mMaxChannels;
|
|
}
|
|
|
|
bool ExportPlugin::GetCanMetaData(int index)
|
|
{
|
|
return mFormatInfos[index].mCanMetaData;
|
|
}
|
|
|
|
bool ExportPlugin::IsExtension(const wxString & ext, int index)
|
|
{
|
|
bool isext = false;
|
|
for (int i = index; i < GetFormatCount(); i = GetFormatCount())
|
|
{
|
|
wxString defext = GetExtension(i);
|
|
wxArrayString defexts = GetExtensions(i);
|
|
int indofext = defexts.Index(ext, false);
|
|
if (defext == wxT("") || (indofext != wxNOT_FOUND))
|
|
isext = true;
|
|
}
|
|
return isext;
|
|
}
|
|
|
|
bool ExportPlugin::DisplayOptions(wxWindow * WXUNUSED(parent), int WXUNUSED(format))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
wxWindow *ExportPlugin::OptionsCreate(wxWindow *parent, int WXUNUSED(format))
|
|
{
|
|
wxASSERT(parent); // To justify safenew
|
|
wxPanel *p = safenew wxPanelWrapper(parent, wxID_ANY);
|
|
ShuttleGui S(p, eIsCreatingFromPrefs);
|
|
|
|
S.StartHorizontalLay(wxCENTER);
|
|
{
|
|
S.StartHorizontalLay(wxCENTER, 0);
|
|
{
|
|
S.Prop(1).AddTitle(_("No format specific options"));
|
|
}
|
|
S.EndHorizontalLay();
|
|
}
|
|
S.EndHorizontalLay();
|
|
|
|
return p;
|
|
}
|
|
|
|
//Create a mixer by computing the time warp factor
|
|
std::unique_ptr<Mixer> ExportPlugin::CreateMixer(const WaveTrackConstArray &inputTracks,
|
|
const TimeTrack *timeTrack,
|
|
double startTime, double stopTime,
|
|
int numOutChannels, int outBufferSize, bool outInterleaved,
|
|
double outRate, sampleFormat outFormat,
|
|
bool highQuality, MixerSpec *mixerSpec)
|
|
{
|
|
// MB: the stop time should not be warped, this was a bug.
|
|
return std::make_unique<Mixer>(inputTracks,
|
|
Mixer::WarpOptions(timeTrack),
|
|
startTime, stopTime,
|
|
numOutChannels, outBufferSize, outInterleaved,
|
|
outRate, outFormat,
|
|
highQuality, mixerSpec);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Export
|
|
//----------------------------------------------------------------------------
|
|
|
|
BEGIN_EVENT_TABLE(Exporter, wxEvtHandler)
|
|
EVT_FILECTRL_FILTERCHANGED(wxID_ANY, Exporter::OnFilterChanged)
|
|
END_EVENT_TABLE()
|
|
|
|
Exporter::Exporter()
|
|
{
|
|
mMixerSpec = NULL;
|
|
mBook = NULL;
|
|
|
|
SetFileDialogTitle( _("Export Audio") );
|
|
|
|
RegisterPlugin(New_ExportPCM());
|
|
RegisterPlugin(New_ExportMP3());
|
|
|
|
#ifdef USE_LIBVORBIS
|
|
RegisterPlugin(New_ExportOGG());
|
|
#endif
|
|
|
|
#ifdef USE_LIBFLAC
|
|
RegisterPlugin(New_ExportFLAC());
|
|
#endif
|
|
|
|
#if USE_LIBTWOLAME
|
|
RegisterPlugin(New_ExportMP2());
|
|
#endif
|
|
|
|
// Command line export not available on Windows and Mac platforms
|
|
RegisterPlugin(New_ExportCL());
|
|
|
|
#if defined(USE_FFMPEG)
|
|
RegisterPlugin(New_ExportFFmpeg());
|
|
#endif
|
|
}
|
|
|
|
Exporter::~Exporter()
|
|
{
|
|
if (mMixerSpec) {
|
|
delete mMixerSpec;
|
|
}
|
|
}
|
|
|
|
void Exporter::SetFileDialogTitle( const wxString & DialogTitle )
|
|
{
|
|
// The default title is "Export File"
|
|
mFileDialogTitle = DialogTitle;
|
|
}
|
|
|
|
int Exporter::FindFormatIndex(int exportindex)
|
|
{
|
|
int c = 0;
|
|
for (const auto &pPlugin : mPlugins)
|
|
{
|
|
for (int j = 0; j < pPlugin->GetFormatCount(); j++)
|
|
{
|
|
if (exportindex == c) return j;
|
|
c++;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void Exporter::RegisterPlugin(movable_ptr<ExportPlugin> &&ExportPlugin)
|
|
{
|
|
mPlugins.push_back(std::move(ExportPlugin));
|
|
}
|
|
|
|
const ExportPluginArray &Exporter::GetPlugins()
|
|
{
|
|
return mPlugins;
|
|
}
|
|
|
|
bool Exporter::Process(AudacityProject *project, bool selectedOnly, double t0, double t1)
|
|
{
|
|
// Save parms
|
|
mProject = project;
|
|
mSelectedOnly = selectedOnly;
|
|
mT0 = t0;
|
|
mT1 = t1;
|
|
|
|
// Gather track information
|
|
if (!ExamineTracks()) {
|
|
return false;
|
|
}
|
|
|
|
// Ask user for file name
|
|
if (!GetFilename()) {
|
|
return false;
|
|
}
|
|
|
|
// Check for down mixing
|
|
if (!CheckMix()) {
|
|
return false;
|
|
}
|
|
|
|
// Let user edit MetaData
|
|
if (mPlugins[mFormat]->GetCanMetaData(mSubFormat)) {
|
|
if (!(project->DoEditMetadata(_("Edit Metadata Tags for Export"), _("Exported Tags"), mProject->GetShowId3Dialog()))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Ensure filename doesn't interfere with project files.
|
|
if (!CheckFilename()) {
|
|
return false;
|
|
}
|
|
|
|
// Export the tracks
|
|
bool success = ExportTracks();
|
|
|
|
// Get rid of mixerspec
|
|
if (mMixerSpec) {
|
|
delete mMixerSpec;
|
|
mMixerSpec = NULL;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool Exporter::Process(AudacityProject *project, int numChannels,
|
|
const wxChar *type, const wxString & filename,
|
|
bool selectedOnly, double t0, double t1)
|
|
{
|
|
// Save parms
|
|
mProject = project;
|
|
mChannels = numChannels;
|
|
mFilename = filename;
|
|
mSelectedOnly = selectedOnly;
|
|
mT0 = t0;
|
|
mT1 = t1;
|
|
mActualName = mFilename;
|
|
|
|
int i = -1;
|
|
for (const auto &pPlugin : mPlugins) {
|
|
++i;
|
|
for (int j = 0; j < pPlugin->GetFormatCount(); j++)
|
|
{
|
|
if (pPlugin->GetFormat(j).IsSameAs(type, false))
|
|
{
|
|
mFormat = i;
|
|
mSubFormat = j;
|
|
return ExportTracks();
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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)
|
|
|
|
double earliestBegin = mT1;
|
|
double latestEnd = mT0;
|
|
|
|
const TrackList *tracks = mProject->GetTracks();
|
|
TrackListConstIterator iter1(tracks);
|
|
const Track *tr = iter1.First();
|
|
|
|
while (tr) {
|
|
if (tr->GetKind() == Track::Wave) {
|
|
if ( (tr->GetSelected() || !mSelectedOnly) && !tr->GetMute() ) { // don't count muted tracks
|
|
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 (mNumSelected == 0) {
|
|
wxString message;
|
|
if(mSelectedOnly)
|
|
message = _("All selected audio is muted.");
|
|
else
|
|
message = _("All audio is muted.");
|
|
wxMessageBox(message,
|
|
_("Unable to export"),
|
|
wxOK | wxICON_INFORMATION);
|
|
return false;
|
|
}
|
|
|
|
if (mT0 < earliestBegin)
|
|
mT0 = earliestBegin;
|
|
|
|
if (mT1 > latestEnd)
|
|
mT1 = latestEnd;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Exporter::GetFilename()
|
|
{
|
|
mFormat = -1;
|
|
|
|
wxString maskString;
|
|
wxString defaultFormat = gPrefs->Read(wxT("/Export/Format"),
|
|
wxT("WAV"));
|
|
|
|
mFilterIndex = 0;
|
|
|
|
{
|
|
int i = -1;
|
|
for (const auto &pPlugin : mPlugins) {
|
|
++i;
|
|
for (int j = 0; j < pPlugin->GetFormatCount(); j++)
|
|
{
|
|
maskString += pPlugin->GetMask(j) + wxT("|");
|
|
if (mPlugins[i]->GetFormat(j) == defaultFormat) {
|
|
mFormat = i;
|
|
mSubFormat = j;
|
|
}
|
|
if (mFormat == -1) mFilterIndex++;
|
|
}
|
|
}
|
|
}
|
|
if (mFormat == -1)
|
|
{
|
|
mFormat = 0;
|
|
mFilterIndex = 0;
|
|
}
|
|
maskString.RemoveLast();
|
|
|
|
mFilename.SetPath(gPrefs->Read(wxT("/Export/Path"), ::wxGetCwd()));
|
|
mFilename.SetName(mProject->GetName());
|
|
while (true) {
|
|
// Must reset each iteration
|
|
mBook = NULL;
|
|
|
|
FileDialog fd(mProject,
|
|
mFileDialogTitle,
|
|
mFilename.GetPath(),
|
|
mFilename.GetFullName(),
|
|
maskString,
|
|
wxFD_SAVE | wxRESIZE_BORDER);
|
|
mDialog = &fd;
|
|
mDialog->PushEventHandler(this);
|
|
|
|
fd.SetUserPaneCreator(CreateUserPaneCallback, (wxUIntPtr) this);
|
|
fd.SetFilterIndex(mFilterIndex);
|
|
|
|
int result = fd.ShowModal();
|
|
|
|
mDialog->PopEventHandler();
|
|
|
|
if (result == wxID_CANCEL) {
|
|
return false;
|
|
}
|
|
|
|
mFilename = fd.GetPath();
|
|
if (mFilename == wxT("")) {
|
|
return false;
|
|
}
|
|
|
|
mFormat = fd.GetFilterIndex();
|
|
mFilterIndex = fd.GetFilterIndex();
|
|
|
|
int c = 0;
|
|
int i = -1;
|
|
for (const auto &pPlugin : mPlugins)
|
|
{
|
|
++i;
|
|
for (int j = 0; j < pPlugin->GetFormatCount(); j++)
|
|
{
|
|
if (mFilterIndex == c)
|
|
{
|
|
mFormat = i;
|
|
mSubFormat = j;
|
|
}
|
|
c++;
|
|
}
|
|
}
|
|
|
|
wxString ext = mFilename.GetExt();
|
|
wxString defext = mPlugins[mFormat]->GetExtension(mSubFormat).Lower();
|
|
|
|
//
|
|
// Check the extension - add the default if it's not there,
|
|
// and warn user if it's abnormal.
|
|
//
|
|
if (ext.IsEmpty()) {
|
|
//
|
|
// Make sure the user doesn't accidentally save the file
|
|
// as an extension with no name, like just plain ".wav".
|
|
//
|
|
if (mFilename.GetName().Left(1) == wxT(".")) {
|
|
wxString prompt = _("Are you sure you want to export the file as \"") +
|
|
mFilename.GetFullName() +
|
|
wxT("\"?\n");
|
|
|
|
int action = wxMessageBox(prompt,
|
|
_("Warning"),
|
|
wxYES_NO | wxICON_EXCLAMATION);
|
|
if (action != wxYES) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
mFilename.SetExt(defext);
|
|
}
|
|
else if (!mPlugins[mFormat]->CheckFileName(mFilename, mSubFormat))
|
|
{
|
|
continue;
|
|
}
|
|
else if (!ext.IsEmpty() && !mPlugins[mFormat]->IsExtension(ext,mSubFormat) && ext.CmpNoCase(defext)) {
|
|
wxString prompt;
|
|
prompt.Printf(_("You are about to export a %s file with the name \"%s\".\n\nNormally these files end in \".%s\", and some programs will not open files with nonstandard extensions.\n\nAre you sure you want to export the file under this name?"),
|
|
mPlugins[mFormat]->GetFormat(mSubFormat).c_str(),
|
|
mFilename.GetFullName().c_str(),
|
|
defext.c_str());
|
|
|
|
int action = wxMessageBox(prompt,
|
|
_("Warning"),
|
|
wxYES_NO | wxICON_EXCLAMATION);
|
|
if (action != wxYES) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (mFilename.GetFullPath().Length() >= 256) {
|
|
wxMessageBox(_("Sorry, pathnames longer than 256 characters not supported."));
|
|
continue;
|
|
}
|
|
|
|
// Check to see if we are writing to a path that a missing aliased file existed at.
|
|
// This causes problems for the exporter, so we don't allow it.
|
|
// Overwritting non-missing aliased files is okay.
|
|
// Also, this can only happen for uncompressed audio.
|
|
bool overwritingMissingAlias;
|
|
overwritingMissingAlias = false;
|
|
for (size_t i = 0; i < gAudacityProjects.GetCount(); i++) {
|
|
AliasedFileArray aliasedFiles;
|
|
FindDependencies(gAudacityProjects[i], aliasedFiles);
|
|
for (const auto &aliasedFile : aliasedFiles) {
|
|
if (mFilename.GetFullPath() == aliasedFile.mFileName.GetFullPath() &&
|
|
!mFilename.FileExists()) {
|
|
// Warn and return to the dialog
|
|
wxMessageBox(_("You are attempting to overwrite an aliased file that is missing.\n\
|
|
The file cannot be written because the path is needed to restore the original audio to the project.\n\
|
|
Choose File > Check Dependencies to view the locations of all missing files.\n\
|
|
If you still wish to export, please choose a different filename or folder."));
|
|
overwritingMissingAlias = true;
|
|
}
|
|
}
|
|
}
|
|
if (overwritingMissingAlias)
|
|
continue;
|
|
|
|
if (mFilename.FileExists()) {
|
|
wxString prompt;
|
|
|
|
prompt.Printf(_("A file named \"%s\" already exists. Replace?"),
|
|
mFilename.GetFullPath().c_str());
|
|
|
|
int action = wxMessageBox(prompt,
|
|
_("Warning"),
|
|
wxYES_NO | wxICON_EXCLAMATION);
|
|
if (action != wxYES) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
bool Exporter::CheckFilename()
|
|
{
|
|
//
|
|
// 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 (!mProject->GetDirManager()->EnsureSafeFilename(mFilename))
|
|
return false;
|
|
|
|
gPrefs->Write(wxT("/Export/Format"), mPlugins[mFormat]->GetFormat(mSubFormat));
|
|
gPrefs->Write(wxT("/Export/Path"), mFilename.GetPath());
|
|
gPrefs->Flush();
|
|
|
|
//
|
|
// To be even safer, return a temporary file name based
|
|
// on this one...
|
|
//
|
|
|
|
mActualName = mFilename;
|
|
|
|
int suffix = 0;
|
|
while (mFilename.FileExists()) {
|
|
mFilename.SetName(mActualName.GetName() +
|
|
wxString::Format(wxT("%d"), suffix));
|
|
suffix++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Exporter::DisplayOptions(int index)
|
|
{
|
|
int c = 0;
|
|
int mf = -1, msf = -1;
|
|
int i = -1;
|
|
for (const auto &pPlugin : mPlugins)
|
|
{
|
|
++i;
|
|
for (int j = 0; j < pPlugin->GetFormatCount(); j++)
|
|
{
|
|
if (index == c)
|
|
{
|
|
mf = i;
|
|
msf = j;
|
|
}
|
|
c++;
|
|
}
|
|
}
|
|
// This shouldn't happen...
|
|
if (index >= c) {
|
|
return;
|
|
}
|
|
|
|
#if defined(__WXMSW__)
|
|
mPlugins[mf]->DisplayOptions(mProject, msf);
|
|
#else
|
|
mPlugins[mf]->DisplayOptions(mDialog, msf);
|
|
#endif
|
|
}
|
|
|
|
bool Exporter::CheckMix()
|
|
{
|
|
// Clean up ... should never happen
|
|
if (mMixerSpec) {
|
|
delete mMixerSpec;
|
|
mMixerSpec = NULL;
|
|
}
|
|
|
|
// Detemine if exported file will be stereo or mono or multichannel,
|
|
// and if mixing will occur.
|
|
|
|
int downMix = gPrefs->Read(wxT("/FileFormats/ExportDownMix"), true);
|
|
int exportedChannels = mPlugins[mFormat]->SetNumExportChannels();
|
|
|
|
if (downMix) {
|
|
if (mNumRight > 0 || mNumLeft > 0) {
|
|
mChannels = 2;
|
|
}
|
|
else {
|
|
mChannels = 1;
|
|
}
|
|
if (mChannels > mPlugins[mFormat]->GetMaxChannels(mSubFormat))
|
|
mChannels = mPlugins[mFormat]->GetMaxChannels(mSubFormat);
|
|
|
|
int numLeft = mNumLeft + mNumMono;
|
|
int numRight = mNumRight + mNumMono;
|
|
|
|
if (numLeft > 1 || numRight > 1 || mNumLeft + mNumRight + mNumMono > mChannels) {
|
|
wxString exportFormat = mPlugins[mFormat]->GetFormat(mSubFormat);
|
|
if (exportFormat != wxT("CL") && exportFormat != wxT("FFMPEG") && exportedChannels == -1)
|
|
exportedChannels = mChannels;
|
|
|
|
if (exportedChannels == 1) {
|
|
if (ShowWarningDialog(mProject,
|
|
wxT("MixMono"),
|
|
_("Your tracks will be mixed down to a single mono channel in the exported file."),
|
|
true) == wxID_CANCEL)
|
|
return false;
|
|
}
|
|
else if (exportedChannels == 2) {
|
|
if (ShowWarningDialog(mProject,
|
|
wxT("MixStereo"),
|
|
_("Your tracks will be mixed down to two stereo channels in the exported file."),
|
|
true) == wxID_CANCEL)
|
|
return false;
|
|
}
|
|
else {
|
|
if (ShowWarningDialog(mProject,
|
|
wxT("MixUnknownChannels"),
|
|
_("Your tracks will be mixed down to one exported file according to the encoder settings."),
|
|
true) == wxID_CANCEL)
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (exportedChannels == -1)
|
|
exportedChannels = mPlugins[mFormat]->GetMaxChannels(mSubFormat);
|
|
|
|
ExportMixerDialog md(mProject->GetTracks(),
|
|
mSelectedOnly,
|
|
exportedChannels,
|
|
NULL,
|
|
1,
|
|
_("Advanced Mixing Options"));
|
|
|
|
if (md.ShowModal() != wxID_OK) {
|
|
return false;
|
|
}
|
|
|
|
mMixerSpec = new MixerSpec(*(md.GetMixerSpec()));
|
|
mChannels = mMixerSpec->GetNumChannels();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Exporter::ExportTracks()
|
|
{
|
|
int success;
|
|
|
|
// Keep original in case of failure
|
|
if (mActualName != mFilename) {
|
|
::wxRenameFile(mActualName.GetFullPath(), mFilename.GetFullPath());
|
|
}
|
|
|
|
success = mPlugins[mFormat]->Export(mProject,
|
|
mChannels,
|
|
mActualName.GetFullPath(),
|
|
mSelectedOnly,
|
|
mT0,
|
|
mT1,
|
|
mMixerSpec,
|
|
NULL,
|
|
mSubFormat);
|
|
|
|
if (mActualName != mFilename) {
|
|
// Remove backup
|
|
if (success == eProgressSuccess || success == eProgressStopped) {
|
|
::wxRemoveFile(mFilename.GetFullPath());
|
|
}
|
|
else {
|
|
// Restore original, if needed
|
|
::wxRemoveFile(mActualName.GetFullPath());
|
|
::wxRenameFile(mFilename.GetFullPath(), mActualName.GetFullPath());
|
|
}
|
|
}
|
|
|
|
return (success == eProgressSuccess || success == eProgressStopped);
|
|
}
|
|
|
|
void Exporter::CreateUserPaneCallback(wxWindow *parent, wxUIntPtr userdata)
|
|
{
|
|
Exporter *self = (Exporter *) userdata;
|
|
if (self)
|
|
{
|
|
self->CreateUserPane(parent);
|
|
}
|
|
}
|
|
|
|
void Exporter::CreateUserPane(wxWindow *parent)
|
|
{
|
|
ShuttleGui S(parent, eIsCreating);
|
|
|
|
S.StartVerticalLay();
|
|
{
|
|
S.StartHorizontalLay(wxEXPAND);
|
|
{
|
|
S.StartStatic(_("Format Options"), 1);
|
|
{
|
|
mBook = safenew wxSimplebook(S.GetParent());
|
|
S.AddWindow(mBook, wxEXPAND);
|
|
|
|
for (const auto &pPlugin : mPlugins)
|
|
{
|
|
for (int j = 0; j < pPlugin->GetFormatCount(); j++)
|
|
{
|
|
mBook->AddPage(pPlugin->OptionsCreate(mBook, j), wxEmptyString);
|
|
}
|
|
}
|
|
}
|
|
S.EndStatic();
|
|
}
|
|
S.EndHorizontalLay();
|
|
}
|
|
S.EndVerticalLay();
|
|
|
|
return;
|
|
}
|
|
|
|
void Exporter::OnFilterChanged(wxFileCtrlEvent & evt)
|
|
{
|
|
int index = evt.GetFilterIndex();
|
|
|
|
// On GTK, this event can fire before the userpane is created
|
|
if (mBook == NULL || index < 0 || index >= (int) mBook->GetPageCount())
|
|
{
|
|
return;
|
|
}
|
|
|
|
mBook->ChangeSelection(index);
|
|
}
|
|
|
|
bool Exporter::ProcessFromTimerRecording(AudacityProject *project,
|
|
bool selectedOnly,
|
|
double t0,
|
|
double t1,
|
|
wxFileName fnFile,
|
|
int iFormat,
|
|
int iSubFormat,
|
|
int iFilterIndex)
|
|
{
|
|
// Save parms
|
|
mProject = project;
|
|
mSelectedOnly = selectedOnly;
|
|
mT0 = t0;
|
|
mT1 = t1;
|
|
|
|
// Auto Export Parameters
|
|
mFilename = fnFile;
|
|
mFormat = iFormat;
|
|
mSubFormat = iSubFormat;
|
|
mFilterIndex = iFilterIndex;
|
|
|
|
// Gather track information
|
|
if (!ExamineTracks()) {
|
|
return false;
|
|
}
|
|
|
|
// Check for down mixing
|
|
if (!CheckMix()) {
|
|
return false;
|
|
}
|
|
|
|
// Ensure filename doesn't interfere with project files.
|
|
if (!CheckFilename()) {
|
|
return false;
|
|
}
|
|
|
|
// Export the tracks
|
|
bool success = ExportTracks();
|
|
|
|
// Get rid of mixerspec
|
|
if (mMixerSpec) {
|
|
delete mMixerSpec;
|
|
mMixerSpec = NULL;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
int Exporter::GetAutoExportFormat() {
|
|
return mFormat;
|
|
}
|
|
|
|
int Exporter::GetAutoExportSubFormat() {
|
|
return mSubFormat;
|
|
}
|
|
|
|
int Exporter::GetAutoExportFilterIndex() {
|
|
return mFormat;
|
|
}
|
|
|
|
wxFileName Exporter::GetAutoExportFileName() {
|
|
return mFilename;
|
|
}
|
|
|
|
bool Exporter::SetAutoExportOptions(AudacityProject *project) {
|
|
mFormat = -1;
|
|
mProject = project;
|
|
|
|
if( GetFilename()==false )
|
|
return false;
|
|
|
|
// Let user edit MetaData
|
|
if (mPlugins[mFormat]->GetCanMetaData(mSubFormat)) {
|
|
if (!(project->DoEditMetadata(_("Edit Metadata Tags for Export"),
|
|
_("Exported Tags"), mProject->GetShowId3Dialog()))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// ExportMixerPanel
|
|
//----------------------------------------------------------------------------
|
|
|
|
BEGIN_EVENT_TABLE(ExportMixerPanel, wxPanelWrapper)
|
|
EVT_PAINT(ExportMixerPanel::OnPaint)
|
|
EVT_MOUSE_EVENTS(ExportMixerPanel::OnMouseEvent)
|
|
END_EVENT_TABLE()
|
|
|
|
ExportMixerPanel::ExportMixerPanel( MixerSpec *mixerSpec,
|
|
wxArrayString trackNames,wxWindow *parent, wxWindowID id,
|
|
const wxPoint& pos, const wxSize& size):
|
|
wxPanelWrapper(parent, id, pos, size)
|
|
{
|
|
mBitmap = NULL;
|
|
mWidth = 0;
|
|
mHeight = 0;
|
|
mMixerSpec = mixerSpec;
|
|
mSelectedTrack = mSelectedChannel = -1;
|
|
|
|
mTrackRects = new wxRect[ mMixerSpec->GetNumTracks() ];
|
|
mChannelRects = new wxRect[ mMixerSpec->GetMaxNumChannels() ];
|
|
|
|
mTrackNames = trackNames;
|
|
}
|
|
|
|
ExportMixerPanel::~ExportMixerPanel()
|
|
{
|
|
delete[] mTrackRects;
|
|
delete[] mChannelRects;
|
|
|
|
if (mBitmap) {
|
|
delete mBitmap;
|
|
}
|
|
}
|
|
|
|
//set the font on memDC such that text can fit in specified width and height
|
|
void ExportMixerPanel::SetFont(wxMemoryDC &memDC, const wxString &text, int width,
|
|
int height )
|
|
{
|
|
int l = 0, u = 13, m, w, h;
|
|
wxFont font = memDC.GetFont();
|
|
while( l < u - 1 )
|
|
{
|
|
m = ( l + u ) / 2;
|
|
font.SetPointSize( m );
|
|
memDC.SetFont( font );
|
|
memDC.GetTextExtent( text, &w, &h );
|
|
|
|
if( w < width && h < height )
|
|
l = m;
|
|
else
|
|
u = m;
|
|
}
|
|
font.SetPointSize( l );
|
|
memDC.SetFont( font );
|
|
}
|
|
|
|
void ExportMixerPanel::OnPaint(wxPaintEvent & WXUNUSED(event))
|
|
{
|
|
wxPaintDC dc( this );
|
|
|
|
int width, height;
|
|
GetSize( &width, &height );
|
|
|
|
if( !mBitmap || mWidth != width || mHeight != height )
|
|
{
|
|
if( mBitmap )
|
|
delete mBitmap;
|
|
|
|
mWidth = width;
|
|
mHeight = height;
|
|
mBitmap = new wxBitmap( mWidth, mHeight );
|
|
}
|
|
|
|
wxColour bkgnd = GetBackgroundColour();
|
|
wxBrush bkgndBrush( bkgnd, wxSOLID );
|
|
|
|
wxMemoryDC memDC;
|
|
memDC.SelectObject( *mBitmap );
|
|
|
|
//draw background
|
|
wxRect bkgndRect;
|
|
bkgndRect.x = 0;
|
|
bkgndRect.y = 0;
|
|
bkgndRect.width = mWidth;
|
|
bkgndRect.height = mHeight;
|
|
|
|
memDC.SetBrush( *wxWHITE_BRUSH );
|
|
memDC.SetPen( *wxBLACK_PEN );
|
|
memDC.DrawRectangle( bkgndRect );
|
|
|
|
//box dimensions
|
|
mBoxWidth = mWidth / 6;
|
|
|
|
mTrackHeight = ( mHeight * 3 ) / ( mMixerSpec->GetNumTracks() * 4 );
|
|
if( mTrackHeight > 30 )
|
|
mTrackHeight = 30;
|
|
|
|
mChannelHeight = ( mHeight * 3 ) / ( mMixerSpec->GetNumChannels() * 4 );
|
|
if( mChannelHeight > 30 )
|
|
mChannelHeight = 30;
|
|
|
|
static double PI = 2 * acos( 0.0 );
|
|
double angle = atan( ( 3.0 * mHeight ) / mWidth );
|
|
double radius = mHeight / ( 2.0 * sin( PI - 2.0 * angle ) );
|
|
double totAngle = ( asin( mHeight / ( 2.0 * radius ) ) * 2.0 );
|
|
|
|
//draw tracks
|
|
memDC.SetBrush( AColor::envelopeBrush );
|
|
angle = totAngle / ( mMixerSpec->GetNumTracks() + 1 );
|
|
|
|
int max = 0, w, h;
|
|
for( int i = 1; i < mMixerSpec->GetNumTracks(); i++ )
|
|
if( mTrackNames[ i ].length() > mTrackNames[ max ].length() )
|
|
max = i;
|
|
|
|
SetFont( memDC, mTrackNames[ max ], mBoxWidth, mTrackHeight );
|
|
|
|
for( int i = 0; i < mMixerSpec->GetNumTracks(); i++ )
|
|
{
|
|
mTrackRects[ i ].x = ( int )( mBoxWidth * 2 + radius - radius *
|
|
cos( totAngle / 2.0 - angle * ( i + 1 ) ) - mBoxWidth + 0.5 );
|
|
mTrackRects[ i ].y = ( int )( mHeight * 0.5 - radius *
|
|
sin( totAngle * 0.5 - angle * ( i + 1.0 ) ) -
|
|
0.5 * mTrackHeight + 0.5 );
|
|
|
|
mTrackRects[ i ].width = mBoxWidth;
|
|
mTrackRects[ i ].height = mTrackHeight;
|
|
|
|
memDC.SetPen( mSelectedTrack == i ? *wxRED_PEN : *wxBLACK_PEN );
|
|
memDC.DrawRectangle( mTrackRects[ i ] );
|
|
|
|
memDC.GetTextExtent( mTrackNames[ i ], &w, &h );
|
|
memDC.DrawText( mTrackNames[ i ],
|
|
mTrackRects[ i ].x + ( mBoxWidth - w ) / 2,
|
|
mTrackRects[ i ].y + ( mTrackHeight - h ) / 2 );
|
|
}
|
|
|
|
//draw channels
|
|
memDC.SetBrush( AColor::playRegionBrush[ 0 ] );
|
|
angle = ( asin( mHeight / ( 2.0 * radius ) ) * 2.0 ) /
|
|
( mMixerSpec->GetNumChannels() + 1 );
|
|
|
|
SetFont( memDC, wxT( "Channel: XX" ), mBoxWidth, mChannelHeight );
|
|
memDC.GetTextExtent( wxT( "Channel: XX" ), &w, &h );
|
|
|
|
for( int i = 0; i < mMixerSpec->GetNumChannels(); i++ )
|
|
{
|
|
mChannelRects[ i ].x = ( int )( mBoxWidth * 4 - radius + radius *
|
|
cos( totAngle * 0.5 - angle * ( i + 1 ) ) + 0.5 );
|
|
mChannelRects[ i ].y = ( int )( mHeight * 0.5 - radius *
|
|
sin( totAngle * 0.5 - angle * ( i + 1 ) ) -
|
|
0.5 * mChannelHeight + 0.5 );
|
|
|
|
mChannelRects[ i ].width = mBoxWidth;
|
|
mChannelRects[ i ].height = mChannelHeight;
|
|
|
|
memDC.SetPen( mSelectedChannel == i ? *wxRED_PEN : *wxBLACK_PEN );
|
|
memDC.DrawRectangle( mChannelRects[ i ] );
|
|
|
|
memDC.DrawText( wxString::Format( _( "Channel: %2d" ), i + 1 ),
|
|
mChannelRects[ i ].x + ( mBoxWidth - w ) / 2,
|
|
mChannelRects[ i ].y + ( mChannelHeight - h ) / 2 );
|
|
}
|
|
|
|
//draw links
|
|
memDC.SetPen( wxPen( *wxBLACK, mHeight / 200 ) );
|
|
for( int i = 0; i < mMixerSpec->GetNumTracks(); i++ )
|
|
for( int j = 0; j < mMixerSpec->GetNumChannels(); j++ )
|
|
if( mMixerSpec->mMap[ i ][ j ] )
|
|
AColor::Line(memDC, mTrackRects[ i ].x + mBoxWidth,
|
|
mTrackRects[ i ].y + mTrackHeight / 2, mChannelRects[ j ].x,
|
|
mChannelRects[ j ].y + mChannelHeight / 2 );
|
|
|
|
dc.Blit( 0, 0, mWidth, mHeight, &memDC, 0, 0, wxCOPY, FALSE );
|
|
}
|
|
|
|
double ExportMixerPanel::Distance( wxPoint &a, wxPoint &b )
|
|
{
|
|
return sqrt( pow( a.x - b.x, 2.0 ) + pow( a.y - b.y, 2.0 ) );
|
|
}
|
|
|
|
//checks if p is on the line connecting la, lb with tolerence
|
|
bool ExportMixerPanel::IsOnLine( wxPoint p, wxPoint la, wxPoint lb )
|
|
{
|
|
return Distance( p, la ) + Distance( p, lb ) - Distance( la, lb ) < 0.1;
|
|
}
|
|
|
|
void ExportMixerPanel::OnMouseEvent(wxMouseEvent & event)
|
|
{
|
|
if( event.ButtonDown() )
|
|
{
|
|
bool reset = true;
|
|
//check tracks
|
|
for( int i = 0; i < mMixerSpec->GetNumTracks(); i++ )
|
|
if( mTrackRects[ i ].Contains( event.m_x, event.m_y ) )
|
|
{
|
|
reset = false;
|
|
if( mSelectedTrack == i )
|
|
mSelectedTrack = -1;
|
|
else
|
|
{
|
|
mSelectedTrack = i;
|
|
if( mSelectedChannel != -1 )
|
|
mMixerSpec->mMap[ mSelectedTrack ][ mSelectedChannel ] =
|
|
!mMixerSpec->mMap[ mSelectedTrack ][ mSelectedChannel ];
|
|
}
|
|
goto found;
|
|
}
|
|
|
|
//check channels
|
|
for( int i = 0; i < mMixerSpec->GetNumChannels(); i++ )
|
|
if( mChannelRects[ i ].Contains( event.m_x, event.m_y ) )
|
|
{
|
|
reset = false;
|
|
if( mSelectedChannel == i )
|
|
mSelectedChannel = -1;
|
|
else
|
|
{
|
|
mSelectedChannel = i;
|
|
if( mSelectedTrack != -1 )
|
|
mMixerSpec->mMap[ mSelectedTrack ][ mSelectedChannel ] =
|
|
!mMixerSpec->mMap[ mSelectedTrack ][ mSelectedChannel ];
|
|
}
|
|
goto found;
|
|
}
|
|
|
|
//check links
|
|
for( int i = 0; i < mMixerSpec->GetNumTracks(); i++ )
|
|
for( int j = 0; j < mMixerSpec->GetNumChannels(); j++ )
|
|
if( mMixerSpec->mMap[ i ][ j ] && IsOnLine( wxPoint( event.m_x,
|
|
event.m_y ), wxPoint( mTrackRects[ i ].x + mBoxWidth,
|
|
mTrackRects[ i ].y + mTrackHeight / 2 ),
|
|
wxPoint( mChannelRects[ j ].x, mChannelRects[ j ].y +
|
|
mChannelHeight / 2 ) ) )
|
|
mMixerSpec->mMap[ i ][ j ] = false;
|
|
|
|
found:
|
|
if( reset )
|
|
mSelectedTrack = mSelectedChannel = -1;
|
|
Refresh( false );
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// ExportMixerDialog
|
|
//----------------------------------------------------------------------------
|
|
|
|
enum
|
|
{
|
|
ID_MIXERPANEL = 10001,
|
|
ID_SLIDER_CHANNEL
|
|
};
|
|
|
|
BEGIN_EVENT_TABLE( ExportMixerDialog, wxDialogWrapper )
|
|
EVT_BUTTON( wxID_OK, ExportMixerDialog::OnOk )
|
|
EVT_BUTTON( wxID_CANCEL, ExportMixerDialog::OnCancel )
|
|
EVT_SIZE( ExportMixerDialog::OnSize )
|
|
EVT_SLIDER( ID_SLIDER_CHANNEL, ExportMixerDialog::OnSlider )
|
|
END_EVENT_TABLE()
|
|
|
|
ExportMixerDialog::ExportMixerDialog( const TrackList *tracks, bool selectedOnly,
|
|
int maxNumChannels, wxWindow *parent, wxWindowID id, const wxString &title,
|
|
const wxPoint &position, const wxSize& size, long style ) :
|
|
wxDialogWrapper( parent, id, title, position, size, style | wxRESIZE_BORDER )
|
|
{
|
|
SetName(GetTitle());
|
|
|
|
int numTracks = 0;
|
|
TrackListConstIterator iter( tracks );
|
|
|
|
for( const Track *t = iter.First(); t; t = iter.Next() )
|
|
{
|
|
if( t->GetKind() == Track::Wave && ( t->GetSelected() || !selectedOnly ) && !t->GetMute() )
|
|
{
|
|
numTracks++;
|
|
const wxString sTrackName = (t->GetName()).Left(20);
|
|
if( t->GetChannel() == Track::LeftChannel )
|
|
mTrackNames.Add(sTrackName + _( " - L" ));
|
|
else if( t->GetChannel() == Track::RightChannel )
|
|
mTrackNames.Add(sTrackName + _( " - R" ));
|
|
else
|
|
mTrackNames.Add(sTrackName);
|
|
}
|
|
}
|
|
|
|
// 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 )
|
|
// STF (April 2016): AMR (narrowband) and MP3 may export 1 channel.
|
|
// maxNumChannels = 2;
|
|
maxNumChannels = 1;
|
|
if (maxNumChannels > 32)
|
|
maxNumChannels = 32;
|
|
|
|
mMixerSpec = new MixerSpec(numTracks, maxNumChannels);
|
|
|
|
wxBoxSizer *vertSizer;
|
|
{
|
|
auto uVertSizer = std::make_unique<wxBoxSizer>(wxVERTICAL);
|
|
vertSizer = uVertSizer.get();
|
|
|
|
wxWindow *mixerPanel = safenew ExportMixerPanel(mMixerSpec, mTrackNames, this,
|
|
ID_MIXERPANEL, wxDefaultPosition, wxSize(400, -1));
|
|
mixerPanel->SetName(_("Mixer Panel"));
|
|
vertSizer->Add(mixerPanel, 1, wxEXPAND | wxALIGN_CENTRE | wxALL, 5);
|
|
|
|
{
|
|
auto horSizer = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
|
|
|
|
wxString label;
|
|
label.Printf(_("Output Channels: %2d"), mMixerSpec->GetNumChannels());
|
|
mChannelsText = safenew wxStaticText(this, -1, label);
|
|
horSizer->Add(mChannelsText, 0, wxALIGN_LEFT | wxALL, 5);
|
|
|
|
wxSlider *channels = safenew wxSlider(this, ID_SLIDER_CHANNEL,
|
|
mMixerSpec->GetNumChannels(), 1, mMixerSpec->GetMaxNumChannels(),
|
|
wxDefaultPosition, wxSize(300, -1));
|
|
channels->SetName(label);
|
|
horSizer->Add(channels, 0, wxEXPAND | wxALL, 5);
|
|
|
|
vertSizer->Add(horSizer.release(), 0, wxALIGN_CENTRE | wxALL, 5);
|
|
}
|
|
|
|
vertSizer->Add(CreateStdButtonSizer(this, eCancelButton | eOkButton).release(), 0, wxEXPAND);
|
|
|
|
SetAutoLayout(true);
|
|
SetSizer(uVertSizer.release());
|
|
}
|
|
|
|
vertSizer->Fit( this );
|
|
vertSizer->SetSizeHints( this );
|
|
|
|
SetSizeHints( 640, 480, 20000, 20000 );
|
|
|
|
SetSize( 640, 480 );
|
|
}
|
|
|
|
ExportMixerDialog::~ExportMixerDialog()
|
|
{
|
|
if( mMixerSpec )
|
|
{
|
|
delete mMixerSpec;
|
|
mMixerSpec = NULL;
|
|
}
|
|
}
|
|
|
|
void ExportMixerDialog::OnSize(wxSizeEvent &event)
|
|
{
|
|
ExportMixerPanel *pnl = ( ( ExportMixerPanel* ) FindWindow( ID_MIXERPANEL ) );
|
|
pnl->Refresh( false );
|
|
event.Skip();
|
|
}
|
|
|
|
void ExportMixerDialog::OnSlider( wxCommandEvent & WXUNUSED(event))
|
|
{
|
|
wxSlider *channels = ( wxSlider* )FindWindow( ID_SLIDER_CHANNEL );
|
|
ExportMixerPanel *pnl = ( ( ExportMixerPanel* ) FindWindow( ID_MIXERPANEL ) );
|
|
mMixerSpec->SetNumChannels( channels->GetValue() );
|
|
pnl->Refresh( false );
|
|
wxString label;
|
|
label.Printf( _( "Output Channels: %2d" ), mMixerSpec->GetNumChannels() );
|
|
mChannelsText->SetLabel( label );
|
|
channels->SetName( label );
|
|
}
|
|
|
|
void ExportMixerDialog::OnOk(wxCommandEvent & WXUNUSED(event))
|
|
{
|
|
EndModal( wxID_OK );
|
|
}
|
|
|
|
void ExportMixerDialog::OnCancel(wxCommandEvent & WXUNUSED(event))
|
|
{
|
|
EndModal( wxID_CANCEL );
|
|
}
|
|
|