/********************************************************************** Audacity: A Digital Audio Editor ImportRaw.cpp Dominic Mazzoni *******************************************************************//** \file ImportRaw.cpp \brief Functions for guessing audio type and attempting to read from unknown sample audio data. Implements ImportRawDialog. *//****************************************************************//** \class ImportRawDialog \brief ImportRawDialog prompts you with options such as endianness and sample size to help you importing data of an unknown format. *//*******************************************************************/ #include "../Audacity.h" #include "ImportRaw.h" #include "../DirManager.h" #include "../FileFormats.h" #include "../Prefs.h" #include "../ShuttleGui.h" #include "../UserException.h" #include "../WaveTrack.h" #include "../prefs/QualityPrefs.h" #include "../widgets/ProgressDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // #include "RawAudioGuess.h" #include "FormatClassifier.h" #include "sndfile.h" class ImportRawDialog final : public wxDialogWrapper { public: ImportRawDialog(wxWindow * parent, int encoding, unsigned channels, int offset, double rate); ~ImportRawDialog(); void OnOK(wxCommandEvent & event); void OnCancel(wxCommandEvent & event); void OnPlay(wxCommandEvent & event); void OnChoice(wxCommandEvent & event); // in and out int mEncoding; unsigned mChannels; int mOffset; double mRate; double mPercent; private: wxButton *mOK; wxChoice *mEncodingChoice; wxChoice *mEndianChoice; wxChoice *mChannelChoice; wxTextCtrl *mOffsetText; wxTextCtrl *mPercentText; wxTextCtrl *mRateText; int mNumEncodings; ArrayOf mEncodingSubtype; DECLARE_EVENT_TABLE() }; // This function leaves outTracks empty as an indication of error, // but may also throw FileException to make use of the application's // user visible error reporting. void ImportRaw(wxWindow *parent, const wxString &fileName, TrackFactory *trackFactory, TrackHolders &outTracks) { outTracks.clear(); int encoding = 0; // Guess Format sampleFormat format; sf_count_t offset = 0; double rate = 44100.0; double percent = 100.0; TrackHolders results; auto updateResult = ProgressResult::Success; { SF_INFO sndInfo; unsigned numChannels = 0; try { // Yes, FormatClassifier currently handles filenames in UTF8 format only, that's // a TODO ... FormatClassifier theClassifier(fileName.utf8_str()); encoding = theClassifier.GetResultFormatLibSndfile(); numChannels = theClassifier.GetResultChannels(); offset = 0; } catch (...) { // Something went wrong in FormatClassifier, use defaults instead. encoding = 0; } if (encoding <= 0) { // Unable to guess. Use mono, 16-bit samples with CPU endianness // as the default. encoding = SF_FORMAT_RAW | SF_ENDIAN_CPU | SF_FORMAT_PCM_16; numChannels = 1; offset = 0; } numChannels = std::max(1u, numChannels); ImportRawDialog dlog(parent, encoding, numChannels, (int)offset, rate); dlog.ShowModal(); if (!dlog.GetReturnCode()) return; encoding = dlog.mEncoding; numChannels = dlog.mChannels; rate = dlog.mRate; offset = (sf_count_t)dlog.mOffset; percent = dlog.mPercent; memset(&sndInfo, 0, sizeof(SF_INFO)); sndInfo.samplerate = (int)rate; sndInfo.channels = (int)numChannels; sndInfo.format = encoding | SF_FORMAT_RAW; wxFile f; // will be closed when it goes out of scope SFFile sndFile; if (f.Open(fileName)) { // Even though there is an sf_open() that takes a filename, use the one that // takes a file descriptor since wxWidgets can open a file with a Unicode name and // libsndfile can't (under Windows). sndFile.reset(SFCall(sf_open_fd, f.fd(), SFM_READ, &sndInfo, FALSE)); } if (!sndFile){ char str[1000]; sf_error_str((SNDFILE *)NULL, str, 1000); wxPrintf("%s\n", str); throw FileException{ FileException::Cause::Open, fileName }; } { int result = sf_command(sndFile.get(), SFC_SET_RAW_START_OFFSET, &offset, sizeof(offset)); if (result != 0) { char str[1000]; sf_error_str(sndFile.get(), str, 1000); wxPrintf("%s\n", str); throw FileException{ FileException::Cause::Read, fileName }; } } SFCall(sf_seek, sndFile.get(), 0, SEEK_SET); auto totalFrames = // fraction of a sf_count_t value (sampleCount)(sndInfo.frames * percent / 100.0); // // Sample format: // // In general, go with the user's preferences. However, if // the file is higher-quality, go with a format which preserves // the quality of the original file. // format = QualityPrefs::SampleFormatChoice(); if (format != floatSample && sf_subtype_more_than_16_bits(encoding)) format = floatSample; results.resize(1); auto &channels = results[0]; channels.resize(numChannels); { // iter not used outside this scope. auto iter = channels.begin(); for (decltype(numChannels) c = 0; c < numChannels; ++iter, ++c) *iter = trackFactory->NewWaveTrack(format, rate); } const auto firstChannel = channels.begin()->get(); auto maxBlockSize = firstChannel->GetMaxBlockSize(); SampleBuffer srcbuffer(maxBlockSize * numChannels, format); SampleBuffer buffer(maxBlockSize, format); decltype(totalFrames) framescompleted = 0; if (totalFrames < 0) { wxASSERT(false); totalFrames = 0; } auto msg = XO("Importing %s").Format( wxFileName::FileName(fileName).GetFullName() ); /* i18n-hint: 'Raw' means 'unprocessed' here and should usually be tanslated.*/ ProgressDialog progress(XO("Import Raw"), msg); size_t block; do { block = limitSampleBufferSize( maxBlockSize, totalFrames - framescompleted ); sf_count_t sf_result; if (format == int16Sample) sf_result = SFCall(sf_readf_short, sndFile.get(), (short *)srcbuffer.ptr(), block); else sf_result = SFCall(sf_readf_float, sndFile.get(), (float *)srcbuffer.ptr(), block); if (sf_result >= 0) { block = sf_result; } else { // This is not supposed to happen, sndfile.h says result is always // a count, not an invalid value for error throw FileException{ FileException::Cause::Read, fileName }; } if (block) { auto iter = channels.begin(); for(decltype(numChannels) c = 0; c < numChannels; ++iter, ++c) { if (format==int16Sample) { for(decltype(block) j=0; jget()->Append(buffer.ptr(), (format == int16Sample)?int16Sample:floatSample, block); } framescompleted += block; } updateResult = progress.Update( framescompleted.as_long_long(), totalFrames.as_long_long() ); if (updateResult != ProgressResult::Success) break; } while (block > 0 && framescompleted < totalFrames); } if (updateResult == ProgressResult::Failed || updateResult == ProgressResult::Cancelled) throw UserException{}; if (!results.empty() && !results[0].empty()) { for (const auto &channel : results[0]) channel->Flush(); outTracks.swap(results); } } // // ImportRawDialog // enum { ChoiceID = 9000, PlayID }; BEGIN_EVENT_TABLE(ImportRawDialog, wxDialogWrapper) EVT_BUTTON(wxID_OK, ImportRawDialog::OnOK) EVT_BUTTON(wxID_CANCEL, ImportRawDialog::OnCancel) EVT_BUTTON(PlayID, ImportRawDialog::OnPlay) EVT_CHOICE(ChoiceID, ImportRawDialog::OnChoice) END_EVENT_TABLE() ImportRawDialog::ImportRawDialog(wxWindow * parent, int encoding, unsigned channels, int offset, double rate) : wxDialogWrapper(parent, wxID_ANY, XO("Import Raw Data"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), mEncoding(encoding), mChannels(channels), mOffset(offset), mRate(rate) { wxASSERT(channels >= 1); SetName(); ShuttleGui S(this, eIsCreating); TranslatableStrings encodings; int num; int selection; int endian; int i; num = sf_num_encodings(); mNumEncodings = 0; mEncodingSubtype.reinit(static_cast(num)); selection = 0; for (i=0; iSetLabel(_("&Import")); } S.EndVerticalLay(); Fit(); SetSizeHints(GetSize()); Centre(wxBOTH); } ImportRawDialog::~ImportRawDialog() { } void ImportRawDialog::OnOK(wxCommandEvent & WXUNUSED(event)) { long l; mEncoding = mEncodingSubtype[mEncodingChoice->GetSelection()]; mEncoding += (mEndianChoice->GetSelection() * 0x10000000); mChannels = mChannelChoice->GetSelection() + 1; mOffsetText->GetValue().ToLong(&l); mOffset = l; mPercentText->GetValue().ToDouble(&mPercent); mRateText->GetValue().ToDouble(&mRate); if (mChannels < 1 || mChannels > 16) mChannels = 1; if (mOffset < 0) mOffset = 0; if (mPercent < 0.0) mPercent = 0.0; if (mPercent > 100.0) mPercent = 100.0; if (mRate < 100.0) mRate = 100.0; // Highest preset sample rate supported in Audacity 2.3.0 is 384 kHz if (mRate > 384000.0) mRate = 384000.0; EndModal(true); } void ImportRawDialog::OnCancel(wxCommandEvent & WXUNUSED(event)) { EndModal(false); } void ImportRawDialog::OnPlay(wxCommandEvent & WXUNUSED(event)) { } void ImportRawDialog::OnChoice(wxCommandEvent & WXUNUSED(event)) { SF_INFO info; memset(&info, 0, sizeof(SF_INFO)); mEncoding = mEncodingSubtype[mEncodingChoice->GetSelection()]; mEncoding += (mEndianChoice->GetSelection() * 0x10000000); info.format = mEncoding | SF_FORMAT_RAW; info.channels = mChannelChoice->GetSelection() + 1; info.samplerate = 44100; //mOK = (wxButton *)wxWindow::FindWindowById(wxID_OK, this); if (sf_format_check(&info)) { mOK->Enable(true); return; } // Try it with 1-channel info.channels = 1; if (sf_format_check(&info)) { mChannelChoice->SetSelection(0); mOK->Enable(true); return; } // Otherwise, this is an unsupported format mOK->Enable(false); }