mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-02 16:49:41 +02:00
... It mentions some wxWidgets types in its interface, but these are in the acceptable utility subset of wxBase that we still consider GUI toolkit-neutral.
529 lines
15 KiB
C++
529 lines
15 KiB
C++
/**********************************************************************
|
|
|
|
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 "ImportRaw.h"
|
|
|
|
#include "ImportPlugin.h"
|
|
|
|
#include "../AudioIOBase.h"
|
|
#include "../FileFormats.h"
|
|
#include "Prefs.h"
|
|
#include "../ProjectSettings.h"
|
|
#include "../ShuttleGui.h"
|
|
#include "UserException.h"
|
|
#include "../WaveTrack.h"
|
|
#include "../widgets/ProgressDialog.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <stdint.h>
|
|
#include <vector>
|
|
|
|
#include <wx/crt.h>
|
|
#include <wx/defs.h>
|
|
#include <wx/button.h>
|
|
#include <wx/choice.h>
|
|
#include <wx/combobox.h>
|
|
#include <wx/intl.h>
|
|
#include <wx/panel.h>
|
|
#include <wx/sizer.h>
|
|
#include <wx/stattext.h>
|
|
#include <wx/textctrl.h>
|
|
#include <wx/timer.h>
|
|
|
|
// #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;
|
|
wxComboBox *mRateText;
|
|
|
|
int mNumEncodings;
|
|
ArrayOf<int> 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(const AudacityProject &project, wxWindow *parent, const wxString &fileName,
|
|
WaveTrackFactory *trackFactory, TrackHolders &outTracks)
|
|
{
|
|
outTracks.clear();
|
|
int encoding = 0; // Guess 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;
|
|
}
|
|
|
|
rate = ProjectSettings::Get( project ).GetRate();
|
|
|
|
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<SNDFILE*>(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_count_t>(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.
|
|
//
|
|
|
|
auto format = ImportFileHandle::ChooseFormat(
|
|
sf_subtype_to_effective_format(encoding));
|
|
|
|
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 translated.*/
|
|
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_count_t>(sf_readf_short, sndFile.get(), (short *)srcbuffer.ptr(), block);
|
|
else
|
|
sf_result = SFCall<sf_count_t>(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; j<block; j++)
|
|
((short *)buffer.ptr())[j] =
|
|
((short *)srcbuffer.ptr())[numChannels*j+c];
|
|
}
|
|
else {
|
|
for(decltype(block) j=0; j<block; j++)
|
|
((float *)buffer.ptr())[j] =
|
|
((float *)srcbuffer.ptr())[numChannels*j+c];
|
|
}
|
|
|
|
iter->get()->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<size_t>(num));
|
|
|
|
selection = 0;
|
|
for (i=0; i<num; i++) {
|
|
SF_INFO info;
|
|
|
|
memset(&info, 0, sizeof(SF_INFO));
|
|
|
|
int subtype = sf_encoding_index_to_subtype(i);
|
|
info.format = SF_FORMAT_RAW + SF_ENDIAN_LITTLE + subtype;
|
|
info.channels = 1;
|
|
info.samplerate = 44100;
|
|
|
|
if (sf_format_check(&info)) {
|
|
mEncodingSubtype[mNumEncodings] = subtype;
|
|
encodings.push_back( Verbatim( sf_encoding_index_name(i) ) );
|
|
|
|
if ((mEncoding & SF_FORMAT_SUBMASK) == subtype)
|
|
selection = mNumEncodings;
|
|
|
|
mNumEncodings++;
|
|
}
|
|
}
|
|
|
|
TranslatableStrings endians{
|
|
/* i18n-hint: Refers to byte-order. Don't translate "endianness" if you don't
|
|
know the correct technical word. */
|
|
XO("No endianness") ,
|
|
/* i18n-hint: Refers to byte-order. Don't translate this if you don't
|
|
know the correct technical word. */
|
|
XO("Little-endian") ,
|
|
/* i18n-hint: Refers to byte-order. Don't translate this if you don't
|
|
know the correct technical word. */
|
|
XO("Big-endian") ,
|
|
/* i18n-hint: Refers to byte-order. Don't translate "endianness" if you don't
|
|
know the correct technical word. */
|
|
XO("Default endianness") ,
|
|
};
|
|
|
|
switch (mEncoding & (SF_FORMAT_ENDMASK))
|
|
{
|
|
default:
|
|
case SF_ENDIAN_FILE:
|
|
endian = 0;
|
|
break;
|
|
case SF_ENDIAN_LITTLE:
|
|
endian = 1;
|
|
break;
|
|
case SF_ENDIAN_BIG:
|
|
endian = 2;
|
|
break;
|
|
case SF_ENDIAN_CPU:
|
|
endian = 3;
|
|
break;
|
|
}
|
|
|
|
TranslatableStrings chans{
|
|
XO("1 Channel (Mono)") ,
|
|
XO("2 Channels (Stereo)") ,
|
|
};
|
|
for (i=2; i<16; i++) {
|
|
chans.push_back( XO("%d Channels").Format( i + 1 ) );
|
|
}
|
|
|
|
S.StartVerticalLay(false);
|
|
{
|
|
S.SetBorder(5);
|
|
S.StartTwoColumn();
|
|
{
|
|
mEncodingChoice = S.Id(ChoiceID).AddChoice(XXO("Encoding:"),
|
|
encodings,
|
|
selection);
|
|
mEndianChoice = S.Id(ChoiceID).AddChoice(XXO("Byte order:"),
|
|
endians,
|
|
endian);
|
|
mChannelChoice = S.Id(ChoiceID).AddChoice(XXO("Channels:"),
|
|
chans,
|
|
mChannels - 1);
|
|
}
|
|
S.EndTwoColumn();
|
|
|
|
S.SetBorder(5);
|
|
S.StartMultiColumn(3);
|
|
{
|
|
// Offset text
|
|
/* i18n-hint: (noun)*/
|
|
mOffsetText = S.AddTextBox(XXO("Start offset:"),
|
|
wxString::Format(wxT("%d"), mOffset),
|
|
12);
|
|
S.AddUnits(XO("bytes"));
|
|
|
|
// Percent text
|
|
mPercentText = S.AddTextBox(XXO("Amount to import:"),
|
|
wxT("100"),
|
|
12);
|
|
S.AddUnits(XO("%"));
|
|
|
|
// Rate text
|
|
wxArrayStringEx rates;
|
|
for (int i = 0; i < AudioIOBase::NumStandardRates; i++) {
|
|
rates.Add(
|
|
wxString::Format(wxT("%d"), AudioIOBase::StandardRates[i]));
|
|
}
|
|
|
|
/* i18n-hint: (noun)*/
|
|
mRateText = S.AddCombo(XXO("Sample rate:"),
|
|
wxString::Format(wxT("%d"), (int)mRate),
|
|
rates);
|
|
/* i18n-hint: This is the abbreviation for "Hertz", or
|
|
cycles per second. */
|
|
S.AddUnits(XO("Hz"));
|
|
}
|
|
S.EndMultiColumn();
|
|
|
|
//
|
|
// Preview Pane goes here
|
|
//
|
|
|
|
S.AddStandardButtons();
|
|
// Find the OK button, and change its text to 'Import'.
|
|
// We MUST set mOK because it is used later.
|
|
mOK = (wxButton *)wxWindow::FindWindowById(wxID_OK, this);
|
|
mOK->SetLabel(_("&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);
|
|
}
|