Audacity  2.2.2
ImportRaw.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  ImportRaw.cpp
6 
7  Dominic Mazzoni
8 
9 *******************************************************************//****************************************************************//*******************************************************************/
22 
23 
24 #include "../Audacity.h"
25 #include "ImportRaw.h"
26 
27 #include "Import.h"
28 
29 #include "../DirManager.h"
30 #include "../FileException.h"
31 #include "../FileFormats.h"
32 #include "../Internat.h"
33 #include "../Prefs.h"
34 #include "../ShuttleGui.h"
35 #include "../UserException.h"
36 #include "../WaveTrack.h"
37 
38 #include <cmath>
39 #include <cstdio>
40 #include <stdint.h>
41 #include <vector>
42 
43 #include <wx/crt.h>
44 #include <wx/defs.h>
45 #include <wx/button.h>
46 #include <wx/choice.h>
47 #include <wx/intl.h>
48 #include <wx/panel.h>
49 #include <wx/progdlg.h>
50 #include <wx/sizer.h>
51 #include <wx/stattext.h>
52 #include <wx/textctrl.h>
53 #include <wx/timer.h>
54 
55 // #include "RawAudioGuess.h"
56 #include "MultiFormatReader.h"
57 #include "FormatClassifier.h"
58 
59 #include "sndfile.h"
60 
61 class ImportRawDialog final : public wxDialogWrapper {
62 
63  public:
64  ImportRawDialog(wxWindow * parent,
65  int encoding, unsigned channels,
66  int offset, double rate);
68 
69  void OnOK(wxCommandEvent & event);
70  void OnCancel(wxCommandEvent & event);
71  void OnPlay(wxCommandEvent & event);
72  void OnChoice(wxCommandEvent & event);
73 
74  // in and out
75  int mEncoding;
76  unsigned mChannels;
77  int mOffset;
78  double mRate;
79  double mPercent;
80 
81  private:
82 
83  wxButton *mOK;
84  wxChoice *mEncodingChoice;
85  wxChoice *mEndianChoice;
86  wxChoice *mChannelChoice;
87  wxTextCtrl *mOffsetText;
88  wxTextCtrl *mPercentText;
89  wxTextCtrl *mRateText;
90 
93 
94  DECLARE_EVENT_TABLE()
95 };
96 
97 // This function leaves outTracks empty as an indication of error,
98 // but may also throw FileException to make use of the application's
99 // user visible error reporting.
100 void ImportRaw(wxWindow *parent, const wxString &fileName,
101  TrackFactory *trackFactory, TrackHolders &outTracks)
102 {
103  outTracks.clear();
104  int encoding = 0; // Guess Format
105  sampleFormat format;
106  sf_count_t offset = 0;
107  double rate = 44100.0;
108  double percent = 100.0;
109  TrackHolders channels;
110  auto updateResult = ProgressResult::Success;
111 
112  {
113  SF_INFO sndInfo;
114  int result;
115 
116  unsigned numChannels = 0;
117 
118  try {
119  // Yes, FormatClassifier currently handles filenames in UTF8 format only, that's
120  // a TODO ...
121  FormatClassifier theClassifier(fileName.utf8_str());
122  encoding = theClassifier.GetResultFormatLibSndfile();
123  numChannels = theClassifier.GetResultChannels();
124  offset = 0;
125  } catch (...) {
126  // Something went wrong in FormatClassifier, use defaults instead.
127  encoding = 0;
128  }
129 
130  if (encoding <= 0) {
131  // Unable to guess. Use mono, 16-bit samples with CPU endianness
132  // as the default.
133  encoding = SF_FORMAT_RAW | SF_ENDIAN_CPU | SF_FORMAT_PCM_16;
134  numChannels = 1;
135  offset = 0;
136  }
137 
138  numChannels = std::max(1u, numChannels);
139  ImportRawDialog dlog(parent, encoding, numChannels, (int)offset, rate);
140  dlog.ShowModal();
141  if (!dlog.GetReturnCode())
142  return;
143 
144  encoding = dlog.mEncoding;
145  numChannels = dlog.mChannels;
146  rate = dlog.mRate;
147  offset = (sf_count_t)dlog.mOffset;
148  percent = dlog.mPercent;
149 
150  memset(&sndInfo, 0, sizeof(SF_INFO));
151  sndInfo.samplerate = (int)rate;
152  sndInfo.channels = (int)numChannels;
153  sndInfo.format = encoding | SF_FORMAT_RAW;
154 
155  wxFile f; // will be closed when it goes out of scope
156  SFFile sndFile;
157 
158  if (f.Open(fileName)) {
159  // Even though there is an sf_open() that takes a filename, use the one that
160  // takes a file descriptor since wxWidgets can open a file with a Unicode name and
161  // libsndfile can't (under Windows).
162  sndFile.reset(SFCall<SNDFILE*>(sf_open_fd, f.fd(), SFM_READ, &sndInfo, FALSE));
163  }
164 
165  if (!sndFile){
166  char str[1000];
167  sf_error_str((SNDFILE *)NULL, str, 1000);
168  wxPrintf("%s\n", str);
169 
170  throw FileException{ FileException::Cause::Open, fileName };
171  }
172 
173  result = sf_command(sndFile.get(), SFC_SET_RAW_START_OFFSET, &offset, sizeof(offset));
174  if (result != 0) {
175  char str[1000];
176  sf_error_str(sndFile.get(), str, 1000);
177  wxPrintf("%s\n", str);
178 
179  throw FileException{ FileException::Cause::Read, fileName };
180  }
181 
182  SFCall<sf_count_t>(sf_seek, sndFile.get(), 0, SEEK_SET);
183 
184  auto totalFrames =
185  // fraction of a sf_count_t value
186  (sampleCount)(sndInfo.frames * percent / 100.0);
187 
188  //
189  // Sample format:
190  //
191  // In general, go with the user's preferences. However, if
192  // the file is higher-quality, go with a format which preserves
193  // the quality of the original file.
194  //
195 
196  format = (sampleFormat)
197  gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample);
198 
199  if (format != floatSample &&
201  format = floatSample;
202 
203  channels.resize(numChannels);
204 
205  auto iter = channels.begin();
206  for (decltype(numChannels) c = 0; c < numChannels; ++iter, ++c) {
207  const auto channel =
208  (*iter = trackFactory->NewWaveTrack(format, rate)).get();
209 
210  if (numChannels > 1)
211  switch (c) {
212  case 0:
213  channel->SetChannel(Track::LeftChannel);
214  break;
215  case 1:
216  channel->SetChannel(Track::RightChannel);
217  break;
218  default:
219  channel->SetChannel(Track::MonoChannel);
220  }
221  }
222 
223  const auto firstChannel = channels.begin()->get();
224  if (numChannels == 2) {
225  firstChannel->SetLinked(true);
226  }
227 
228  auto maxBlockSize = firstChannel->GetMaxBlockSize();
229 
230  SampleBuffer srcbuffer(maxBlockSize * numChannels, format);
231  SampleBuffer buffer(maxBlockSize, format);
232 
233  decltype(totalFrames) framescompleted = 0;
234  if (totalFrames < 0) {
235  wxASSERT(false);
236  totalFrames = 0;
237  }
238 
239  wxString msg;
240 
241  msg.Printf(_("Importing %s"), wxFileName::FileName(fileName).GetFullName());
242 
243  /* i18n-hint: 'Raw' means 'unprocessed' here and should usually be tanslated.*/
244  ProgressDialog progress(_("Import Raw"), msg);
245 
246  size_t block;
247  do {
248  block =
249  limitSampleBufferSize( maxBlockSize, totalFrames - framescompleted );
250 
251  sf_count_t result;
252  if (format == int16Sample)
253  result = SFCall<sf_count_t>(sf_readf_short, sndFile.get(), (short *)srcbuffer.ptr(), block);
254  else
255  result = SFCall<sf_count_t>(sf_readf_float, sndFile.get(), (float *)srcbuffer.ptr(), block);
256 
257  if (result >= 0) {
258  block = result;
259  }
260  else {
261  // This is not supposed to happen, sndfile.h says result is always
262  // a count, not an invalid value for error
263  throw FileException{ FileException::Cause::Read, fileName };
264  }
265 
266  if (block) {
267  auto iter = channels.begin();
268  for(decltype(numChannels) c = 0; c < numChannels; ++iter, ++c) {
269  if (format==int16Sample) {
270  for(decltype(block) j=0; j<block; j++)
271  ((short *)buffer.ptr())[j] =
272  ((short *)srcbuffer.ptr())[numChannels*j+c];
273  }
274  else {
275  for(decltype(block) j=0; j<block; j++)
276  ((float *)buffer.ptr())[j] =
277  ((float *)srcbuffer.ptr())[numChannels*j+c];
278  }
279 
280  iter->get()->Append(buffer.ptr(), (format == int16Sample)?int16Sample:floatSample, block);
281  }
282  framescompleted += block;
283  }
284 
285  updateResult = progress.Update(
286  framescompleted.as_long_long(),
287  totalFrames.as_long_long()
288  );
289  if (updateResult != ProgressResult::Success)
290  break;
291 
292  } while (block > 0 && framescompleted < totalFrames);
293  }
294 
295  if (updateResult == ProgressResult::Failed || updateResult == ProgressResult::Cancelled)
296  throw UserException{};
297 
298  for (const auto &channel : channels)
299  channel->Flush();
300  outTracks.swap(channels);
301 }
302 
303 //
304 // ImportRawDialog
305 //
306 
307 enum {
308  ChoiceID = 9000,
310 };
311 
312 BEGIN_EVENT_TABLE(ImportRawDialog, wxDialogWrapper)
313  EVT_BUTTON(wxID_OK, ImportRawDialog::OnOK)
314  EVT_BUTTON(wxID_CANCEL, ImportRawDialog::OnCancel)
316  EVT_CHOICE(ChoiceID, ImportRawDialog::OnChoice)
318 
319 ImportRawDialog::ImportRawDialog(wxWindow * parent,
320  int encoding, unsigned channels,
321  int offset, double rate)
322 : wxDialogWrapper(parent, wxID_ANY, _("Import Raw Data"),
323  wxDefaultPosition, wxDefaultSize,
324  wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
325  mEncoding(encoding),
326  mChannels(channels),
327  mOffset(offset),
328  mRate(rate)
329 {
330  wxASSERT(channels >= 1);
331 
332  SetName(GetTitle());
333 
334  ShuttleGui S(this, eIsCreating);
335  wxArrayString encodings;
336  wxArrayString endians;
337  wxArrayString chans;
338  int num;
339  int selection;
340  int endian;
341  int i;
342 
343  num = sf_num_encodings();
344  mNumEncodings = 0;
345  mEncodingSubtype.reinit(static_cast<size_t>(num));
346 
347  selection = 0;
348  for (i=0; i<num; i++) {
349  SF_INFO info;
350 
351  memset(&info, 0, sizeof(SF_INFO));
352 
353  int subtype = sf_encoding_index_to_subtype(i);
354  info.format = SF_FORMAT_RAW + SF_ENDIAN_LITTLE + subtype;
355  info.channels = 1;
356  info.samplerate = 44100;
357 
358  if (sf_format_check(&info)) {
359  mEncodingSubtype[mNumEncodings] = subtype;
360  encodings.Add(sf_encoding_index_name(i));
361 
362  if ((mEncoding & SF_FORMAT_SUBMASK) == subtype)
363  selection = mNumEncodings;
364 
365  mNumEncodings++;
366  }
367  }
368 
369  /* i18n-hint: Refers to byte-order. Don't translate "endianness" if you don't
370  know the correct technical word. */
371  endians.Add(_("No endianness"));
372  /* i18n-hint: Refers to byte-order. Don't translate this if you don't
373  know the correct technical word. */
374  endians.Add(_("Little-endian"));
375  /* i18n-hint: Refers to byte-order. Don't translate this if you don't
376  know the correct technical word. */
377  endians.Add(_("Big-endian"));
378  /* i18n-hint: Refers to byte-order. Don't translate "endianness" if you don't
379  know the correct technical word. */
380  endians.Add(_("Default endianness"));
381 
382  switch (mEncoding & (SF_FORMAT_ENDMASK))
383  {
384  default:
385  case SF_ENDIAN_FILE:
386  endian = 0;
387  break;
388  case SF_ENDIAN_LITTLE:
389  endian = 1;
390  break;
391  case SF_ENDIAN_BIG:
392  endian = 2;
393  break;
394  case SF_ENDIAN_CPU:
395  endian = 3;
396  break;
397  }
398 
399  chans.Add(_("1 Channel (Mono)"));
400  chans.Add(_("2 Channels (Stereo)"));
401  for (i=2; i<16; i++) {
402  chans.Add(wxString::Format(_("%d Channels"), i + 1));
403  }
404 
405  S.StartVerticalLay(false);
406  {
407  S.SetBorder(5);
408  S.StartTwoColumn();
409  {
410  mEncodingChoice = S.Id(ChoiceID).AddChoice(_("Encoding:"),
411  encodings[selection],
412  &encodings);
413  mEndianChoice = S.Id(ChoiceID).AddChoice(_("Byte order:"),
414  endians[endian],
415  &endians);
416  mChannelChoice = S.Id(ChoiceID).AddChoice(_("Channels:"),
417  chans[mChannels-1],
418  &chans);
419  }
420  S.EndTwoColumn();
421 
422  S.SetBorder(5);
423  S.StartMultiColumn(3);
424  {
425  // Offset text
426  /* i18n-hint: (noun)*/
427  mOffsetText = S.AddTextBox(_("Start offset:"),
428  wxString::Format(wxT("%d"), mOffset),
429  12);
430  S.AddUnits(_("bytes"));
431 
432  // Percent text
433  mPercentText = S.AddTextBox(_("Amount to import:"),
434  wxT("100"),
435  12);
436  S.AddUnits(_("%"));
437 
438  // Rate text
439  /* i18n-hint: (noun)*/
440  mRateText = S.AddTextBox(_("Sample rate:"),
441  wxString::Format(wxT("%d"), (int)mRate),
442  12);
443  /* i18n-hint: This is the abbreviation for "Hertz", or
444  cycles per second. */
445  S.AddUnits(_("Hz"));
446  }
447  S.EndMultiColumn();
448 
449  //
450  // Preview Pane goes here
451  //
452 
453  S.AddStandardButtons();
454  // Find the OK button, and change its text to 'Import'.
455  // We MUST set mOK because it is used later.
456  mOK = (wxButton *)wxWindow::FindWindowById(wxID_OK, this);
457  mOK->SetLabel(_("&Import"));
458  }
459  S.EndVerticalLay();
460 
461  Fit();
462  SetSizeHints(GetSize());
463 
464  Centre(wxBOTH);
465 }
466 
468 {
469 }
470 
471 void ImportRawDialog::OnOK(wxCommandEvent & WXUNUSED(event))
472 {
473  long l;
474 
475  mEncoding = mEncodingSubtype[mEncodingChoice->GetSelection()];
476  mEncoding += (mEndianChoice->GetSelection() * 0x10000000);
477  mChannels = mChannelChoice->GetSelection() + 1;
478  mOffsetText->GetValue().ToLong(&l);
479  mOffset = l;
480  mPercentText->GetValue().ToDouble(&mPercent);
481  mRateText->GetValue().ToDouble(&mRate);
482 
483  if (mChannels < 1 || mChannels > 16)
484  mChannels = 1;
485  if (mOffset < 0)
486  mOffset = 0;
487  if (mPercent < 0.0)
488  mPercent = 0.0;
489  if (mPercent > 100.0)
490  mPercent = 100.0;
491  if (mRate < 100.0)
492  mRate = 100.0;
493  if (mRate > 100000.0)
494  mRate = 100000.0;
495 
496  EndModal(true);
497 }
498 
499 void ImportRawDialog::OnCancel(wxCommandEvent & WXUNUSED(event))
500 {
501  EndModal(false);
502 }
503 
504 void ImportRawDialog::OnPlay(wxCommandEvent & WXUNUSED(event))
505 {
506 }
507 
508 void ImportRawDialog::OnChoice(wxCommandEvent & WXUNUSED(event))
509 {
510  SF_INFO info;
511 
512  memset(&info, 0, sizeof(SF_INFO));
513 
514  mEncoding = mEncodingSubtype[mEncodingChoice->GetSelection()];
515  mEncoding += (mEndianChoice->GetSelection() * 0x10000000);
516 
517  info.format = mEncoding | SF_FORMAT_RAW;
518  info.channels = mChannelChoice->GetSelection() + 1;
519  info.samplerate = 44100;
520 
521  //mOK = (wxButton *)wxWindow::FindWindowById(wxID_OK, this);
522  if (sf_format_check(&info)) {
523  mOK->Enable(true);
524  return;
525  }
526 
527  // Try it with 1-channel
528  info.channels = 1;
529  if (sf_format_check(&info)) {
530  mChannelChoice->SetSelection(0);
531  mOK->Enable(true);
532  return;
533  }
534 
535  // Otherwise, this is an unsupported format
536  mOK->Enable(false);
537 }
void OnPlay(wxCommandEvent &event)
Definition: ImportRaw.cpp:504
void OnOK(wxCommandEvent &event)
Definition: ImportRaw.cpp:471
wxString sf_encoding_index_name(int i)
Get the string name of the data encoding of the requested format.
Definition: FileFormats.cpp:84
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:366
ProgressDialog Class.
wxChoice * mEndianChoice
Definition: ImportRaw.cpp:85
wxTextCtrl * mRateText
Definition: ImportRaw.cpp:89
bool sf_subtype_more_than_16_bits(unsigned int format)
wxButton * mOK
Definition: ImportRaw.cpp:83
void EndMultiColumn()
ArrayOf< int > mEncodingSubtype
Definition: ImportRaw.cpp:92
FormatClassifier classifies the sample format and endianness of raw audio files.
ImportRawDialog prompts you with options such as endianness and sample size to help you importing dat...
Definition: ImportRaw.cpp:61
Used to create a WaveTrack, or a LabelTrack.. Implementation of the functions of this class are dispe...
Definition: Track.h:708
void AddUnits(const wxString &Prompt)
Left aligned text string.
Definition: ShuttleGui.cpp:229
unsigned mChannels
Definition: ImportRaw.cpp:76
void EndVerticalLay()
Definition: ShuttleGui.cpp:991
wxFileConfig * gPrefs
Definition: Prefs.cpp:72
wxTextCtrl * AddTextBox(const wxString &Caption, const wxString &Value, const int nChars)
Definition: ShuttleGui.cpp:493
wxTextCtrl * mOffsetText
Definition: ImportRaw.cpp:87
int format
Definition: ExportPCM.cpp:56
void StartTwoColumn()
Definition: ShuttleGui.h:125
int sf_num_encodings()
Get the number of data encodings libsndfile supports (in any container or none.
Definition: FileFormats.cpp:75
ImportRawDialog(wxWindow *parent, int encoding, unsigned channels, int offset, double rate)
Definition: ImportRaw.cpp:319
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
Definition: ShuttleGui.cpp:998
wxChoice * AddChoice(const wxString &Prompt, const wxString &Selected, const wxArrayString *pChoices)
Definition: ShuttleGui.cpp:331
ShuttleGui & Id(int id)
ProgressResult Update(int value, const wxString &message=wxEmptyString)
wxChoice * mEncodingChoice
Definition: ImportRaw.cpp:84
samplePtr ptr() const
Definition: SampleFormat.h:81
unsigned int sf_encoding_index_to_subtype(int i)
Definition: FileFormats.cpp:95
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
std::unique_ptr< WaveTrack > NewWaveTrack(sampleFormat format=(sampleFormat) 0, double rate=0)
Definition: WaveTrack.cpp:78
wxChoice * mChannelChoice
Definition: ImportRaw.cpp:86
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom"))), OnMoveTrack) void TrackMenuTable::OnSetName(wxCommandEvent &)
void OnCancel(wxCommandEvent &event)
Definition: ImportRaw.cpp:499
void AddStandardButtons(long buttons=eOkButton|eCancelButton, wxButton *extra=NULL)
void OnChoice(wxCommandEvent &event)
Definition: ImportRaw.cpp:508
END_EVENT_TABLE()
std::vector< std::unique_ptr< WaveTrack >> TrackHolders
Definition: ImportRaw.h:42
void SetBorder(int Border)
Definition: ShuttleGui.h:251
void ImportRaw(wxWindow *parent, const wxString &fileName, TrackFactory *trackFactory, TrackHolders &outTracks)
Definition: ImportRaw.cpp:100
void EndTwoColumn()
Definition: ShuttleGui.h:126
wxTextCtrl * mPercentText
Definition: ImportRaw.cpp:88
void StartVerticalLay(int iProp=1)
Definition: ShuttleGui.cpp:982