mirror of
https://github.com/cookiengineer/audacity
synced 2025-08-08 08:01:19 +02:00
!!! THERE WILL NO DOUBT BE BUGS !!! This is a big one and there's still several things to complete. Just want to get this in the wild to start receiving feedback. One big thing right now is that it will NOT load pre-aup3 files. An importer is on the way for that.
521 lines
15 KiB
C++
521 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 "../Audacity.h"
|
|
#include "ImportRaw.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 <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/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;
|
|
wxTextCtrl *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(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<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.
|
|
//
|
|
|
|
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_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
|
|
/* i18n-hint: (noun)*/
|
|
mRateText = S.AddTextBox(XXO("Sample rate:"),
|
|
wxString::Format(wxT("%d"), (int)mRate),
|
|
12);
|
|
/* 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);
|
|
}
|