diff --git a/cmake-proxies/cmake-modules/DependenciesList.cmake b/cmake-proxies/cmake-modules/DependenciesList.cmake
index a235cd1f7882ea8f6c5a6c72409c41958bb4990a..97a11e2330cf45f9a2e8ffa1870b5a4ae867d00a 100644
--- a/cmake-proxies/cmake-modules/DependenciesList.cmake
+++ b/cmake-proxies/cmake-modules/DependenciesList.cmake
@@ -11,15 +11,6 @@ audacity_find_package(libmp3lame REQUIRED)
 
 audacity_find_package(mpg123 OPTION_NAME libmpg123)
 
-if( NOT ${_OPT}use_libmpg123 STREQUAL "off" )
-   # If we are building against libmpg123, we need to drop
-   # the previos configuration, which may used libmad
-   set( USE_LIBMAD OFF CACHE INTERNAL "" FORCE )
-   set( ${_OPT}use_libmad "off" )
-else()
-   audacity_find_package(libmad)
-endif()
-
 audacity_find_package(libid3tag)
 
 audacity_find_package(WavPack)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 3a47c93acc75fd63feaad88eae0bd5437a591da2..83d58dc2adc7a21e9308544499bc71d66e0d92e2 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -591,10 +591,6 @@ list( APPEND SOURCES
          import/ImportMIDI.h
       >
 
-      $<$<BOOL:${USE_LIBMAD}>:
-         import/ImportMP3_MAD.cpp
-      >
-
       # Menus
 
       menus/ClipMenus.cpp
@@ -1054,7 +1050,6 @@ set( LIBRARIES
    PUBLIC
       ${CMAKE_REQUIRED_LIBRARIES}
       ZLIB::ZLIB
-      $<$<BOOL:${USE_LIBMAD}>:libmad::libmad>
       $<$<BOOL:${USE_LIBOPUS}>:Opus::opus>
       $<$<BOOL:${USE_MIDI}>:portmidi::portmidi>
       $<$<BOOL:${USE_MIDI}>:portsmf>
@@ -1278,7 +1273,7 @@ else()
          audio/x-flac
       )
    endif()
-   if( USE_LIBMAD OR USE_LIBMPG123)
+   if( USE_LIBMPG123)
       list( APPEND MIMETYPES
          audio/mpeg
       )
diff --git a/src/audacity_config.h.in b/src/audacity_config.h.in
index c2c1021d2156d322ef376179b0376813a66ff1bd..5e24a7fe6af97298e163fcb69188265f5e3118e9 100755
--- a/src/audacity_config.h.in
+++ b/src/audacity_config.h.in
@@ -60,9 +60,6 @@
 /* Define if libid3tag is present */
 #cmakedefine USE_LIBID3TAG 1
 
-/* Define if mp3 support is implemented with the libmad library */
-#cmakedefine USE_LIBMAD 1
-
 /* Define if mp3 support is implemented with the libmpg123 library */
 #cmakedefine USE_LIBMPG123 1
 
diff --git a/src/import/ImportMP3_MAD.cpp b/src/import/ImportMP3_MAD.cpp
deleted file mode 100644
index fa26eaca49dd577d9a2f7edd8f9beb6215e17754..0000000000000000000000000000000000000000
--- a/src/import/ImportMP3_MAD.cpp
+++ /dev/null
@@ -1,1114 +0,0 @@
-/**********************************************************************
-
-  Audacity: A Digital Audio Editor
-
-  ImportMP3_MAD.cpp
-
-  Joshua Haberman
-  Leland Lucius
-
-*//****************************************************************//**
-
-\class MP3ImportFileHandle
-\brief An ImportFileHandle for MP3 data
-
-  Audacity has finally moved to using a single mp3 library on all
-  platforms! It is the high performance, beautifully written libmad
-  (mpeg audio decoder). Finally there is harmony in the mp3 universe.
-
-  Much of this source code is based on 'minimad.c' as distributed
-  with libmad.
-
-*//****************************************************************//**
-
-\class MP3ImportPlugin
-\brief An ImportPlugin for MP3 data
-
-*//*******************************************************************/
-
-
-
-#include <wx/defs.h>
-
-#include "Import.h"
-#include "BasicUI.h"
-#include "ImportPlugin.h"
-#include "Project.h"
-
-#define DESC XO("MP3 files")
-
-static const auto exts =
-{
-   wxT("mp3"),
-   wxT("mp2"),
-   wxT("mpa")
-};
-
-#ifndef USE_LIBMAD
-
-static Importer::RegisteredUnusableImportPlugin registered
-{
-   std::make_unique<UnusableImportPlugin>(DESC, FileExtensions(exts.begin(), exts.end()))
-};
-
-#else
-
-#if defined(__WXMSW__)
-#include <malloc.h>
-#else
-#include <stdlib.h>
-#endif
-
-#include <wx/file.h>
-
-#include "Prefs.h"
-#include "../Tags.h"
-#include "WaveTrack.h"
-#include "../widgets/AudacityMessageBox.h"
-#include "../widgets/ProgressDialog.h"
-
-// PRL:  include these last,
-// and correct some preprocessor namespace pollution from wxWidgets that
-// caused a warning about duplicate definition
-#undef SIZEOF_LONG
-extern "C"
-{
-#include "mad.h"
-
-#ifdef USE_LIBID3TAG
-#include <id3tag.h>
-#endif
-}
-
-// Specifies the number of bytes in the input buffer.  This also controls
-// how many bytes will be scanned when searching for the first MP3 frame.
-#define INPUT_BUFFER_SIZE 65535
-
-// This is the number of decoded samples libmad adds at the beginning
-// (This is an "observed" value.)
-#define MAD_DELAY 529
-
-class MP3ImportPlugin final : public ImportPlugin
-{
-public:
-   MP3ImportPlugin();
-   ~MP3ImportPlugin();
-
-   wxString GetPluginStringID() override;
-   TranslatableString GetPluginFormatDescription() override;
-   std::unique_ptr<ImportFileHandle> Open(const FilePath &Filename, AudacityProject*) override;
-};
-
-using NewChannelGroup = std::vector< std::shared_ptr<WaveTrack> >;
-
-class MP3ImportFileHandle final : public ImportFileHandle
-{
-public:
-   MP3ImportFileHandle(const FilePath &filename);
-   ~MP3ImportFileHandle();
-
-   TranslatableString GetFileDescription() override;
-   ByteCount GetFileUncompressedBytes() override;
-   ProgressResult Import(WaveTrackFactory *trackFactory, TrackHolders &outTracks, Tags *tags) override;
-   wxInt32 GetStreamCount() override;
-   const TranslatableStrings &GetStreamInfo() override;
-   void SetStreamUsage(wxInt32 StreamID, bool Use) override;
-
-private:
-   bool Open();
-   void CheckTags();
-   void CheckAPETags(bool atEnd);
-   void CheckID3V1Tags();
-   void CheckID3V2Tags(bool atEnd);
-   void CheckLyrics();
-   bool CheckMP3();
-   bool FillBuffer();
-   void LoadID3(Tags *tags);
-
-   // The MAD callbacks
-
-   static mad_flow input_cb(void *that,
-                            struct mad_stream *stream);
-   mad_flow InputCB(struct mad_stream *stream);
-
-   static mad_flow filter_cb(void *that,
-                             struct mad_stream const *stream,
-                             struct mad_frame *frame);
-   mad_flow FilterCB(struct mad_stream const *stream, struct mad_frame *frame);
-
-   static mad_flow output_cb(void *that,
-                             struct mad_header const *header,
-                             struct mad_pcm *pcm);
-   mad_flow OutputCB(struct mad_header const *header, struct mad_pcm *pcm);
-
-   static mad_flow error_cb(void *that,
-                            struct mad_stream *stream,
-                            struct mad_frame *frame);
-   mad_flow ErrorCB(struct mad_stream *stream, struct mad_frame *frame);
-
-private:
-   mad_decoder mDecoder;
-
-   wxFile mFile;
-   wxFileOffset mFilePos;
-   wxFileOffset mFileLen;
-
-   unsigned char mInputBuffer[INPUT_BUFFER_SIZE + MAD_BUFFER_GUARD];
-   int mInputBufferLen;
-
-   WaveTrackFactory *mTrackFactory;
-   NewChannelGroup mChannels;
-   unsigned mNumChannels;
-
-   ProgressResult mUpdateResult;
-
-   int mDelay;
-   int mPadding;
-
-   bool mHaveID3;
-
-   friend MP3ImportPlugin;
-};
-
-// ============================================================================
-// MP3ImportPlugin
-// ============================================================================
-
-MP3ImportPlugin::MP3ImportPlugin()
-:  ImportPlugin(FileExtensions(exts.begin(), exts.end()))
-{
-}
-
-MP3ImportPlugin::~MP3ImportPlugin()
-{
-}
-
-wxString MP3ImportPlugin::GetPluginStringID()
-{
-   return wxT("libmad");
-}
-
-TranslatableString MP3ImportPlugin::GetPluginFormatDescription()
-{
-   return DESC;
-}
-
-std::unique_ptr<ImportFileHandle> MP3ImportPlugin::Open(
-   const FilePath &Filename, AudacityProject *)
-{
-   auto handle = std::make_unique<MP3ImportFileHandle>(Filename);
-
-   if (!handle->Open())
-   {
-      return nullptr;
-   }
-
-   return handle;
-}
-
-static Importer::RegisteredImportPlugin registered
-{
-   "MP3",
-   std::make_unique<MP3ImportPlugin>()
-};
-
-// ============================================================================
-// MP3ImportFileHandle
-// ============================================================================
-
-MP3ImportFileHandle::MP3ImportFileHandle(const FilePath &filename)
-:  ImportFileHandle(filename)
-{
-}
-
-MP3ImportFileHandle::~MP3ImportFileHandle()
-{
-}
-
-TranslatableString MP3ImportFileHandle::GetFileDescription()
-{
-   return DESC;
-}
-
-auto MP3ImportFileHandle::GetFileUncompressedBytes() -> ByteCount
-{
-   // TODO
-   return 0;
-}
-
-wxInt32 MP3ImportFileHandle::GetStreamCount()
-{
-   return 1;
-}
-
-const TranslatableStrings &MP3ImportFileHandle::GetStreamInfo()
-{
-   static TranslatableStrings empty;
-   return empty;
-}
-
-void MP3ImportFileHandle::SetStreamUsage(wxInt32 WXUNUSED(StreamID), bool WXUNUSED(Use))
-{
-}
-
-ProgressResult MP3ImportFileHandle::Import(WaveTrackFactory *trackFactory,
-                                           TrackHolders &outTracks,
-                                           Tags *tags)
-{
-   outTracks.clear();
-
-   CreateProgress();
-
-   mTrackFactory = trackFactory;
-   mUpdateResult = ProgressResult::Success;
-   mNumChannels = 0;
-
-   // Set delay and padding to best possible in case the LAME tag is not present
-   mDelay = MAD_DELAY;
-   mPadding = 0;
-
-   // Initialize decoder
-   mad_decoder_init(&mDecoder, this, input_cb, 0, filter_cb, output_cb, error_cb, 0);
-
-   // Send the decoder on its way!
-   auto res = mad_decoder_run(&mDecoder, MAD_DECODER_MODE_SYNC);
-
-   // Terminate decoder
-   mad_decoder_finish(&mDecoder);
-
-   // Decoding failed, so pass it on
-   if (res != 0)
-   {
-      return ProgressResult::Failed;
-   }
-
-   // The user canceled the decoding, so bail without saving tracks or tags
-   if (mUpdateResult == ProgressResult::Cancelled)
-   {
-      return mUpdateResult;
-   }
-
-   // Flush and trim the channels
-   for (const auto &channel : mChannels)
-   {
-      channel->Flush();
-
-      // Trim any padding
-      if (mPadding)
-      {
-         double et = channel->GetEndTime();
-         double t1 = et - channel->LongSamplesToTime(mPadding);
-         channel->Clear(t1, et);
-      }
-
-      // And delay
-      if (mDelay)
-      {
-         double st = channel->GetStartTime();
-         double t0 = st + channel->LongSamplesToTime(mDelay);
-         channel->Clear(st, t0);
-      }
-   }
-
-   // Copy the WaveTrack pointers into the Track pointer list that
-   // we are expected to fill
-   outTracks.push_back(std::move(mChannels));
-
-   // Load ID3 tags from the file
-   LoadID3(tags);
-
-   return mUpdateResult;
-}
-
-bool MP3ImportFileHandle::Open()
-{
-   mInputBufferLen = 0;
-   mFilePos = 0;
-   mHaveID3 = false;
-
-   // Open the file
-   if (!mFile.Open(mFilename))
-   {
-      return false;
-   }
-
-   // Get the length of the file
-   mFileLen = mFile.Seek(0, wxFromEnd);
-   if (mFileLen == wxInvalidOffset || mFile.Error())
-   {
-      mFile.Close();
-      return false;
-   }
-
-   if (mFile.Seek(0, wxFromStart) == wxInvalidOffset || mFile.Error())
-   {
-      mFile.Close();
-      return false;
-   }
-
-   // Check for ID3 tags
-   CheckTags();
-
-   // Scan for the first MP3 frame
-   if (!CheckMP3())
-   {
-      mFile.Close();
-      return false;
-   }
-
-   return true;
-}
-
-void MP3ImportFileHandle::CheckTags()
-{
-   // We do this twice to allow them to be in any order
-   for (int i = 0; i < 2; ++i)
-   {
-      CheckAPETags(false);
-      CheckID3V2Tags(false);
-   }
-
-   // We do this twice to allow them to be in any order. Even though ID3v1 is
-   // supposed to at the end, some apps put the v2 tags after the v1 tags.
-   for (int i = 0; i < 2; ++i)
-   {
-      CheckAPETags(true);
-      CheckID3V1Tags();
-      CheckLyrics();
-      CheckID3V2Tags(true);
-   }
-
-   return;
-}
-
-void MP3ImportFileHandle::CheckAPETags(bool atEnd)
-{
-   int offset = atEnd ? mFileLen - 32 : mFilePos;
-
-   // Ensure file is positioned to start of (possible) tags
-   if (mFile.Seek(offset, wxFromStart) == wxInvalidOffset || mFile.Error())
-   {
-      return;
-   }
-
-   // An APE tag header is 32 bytes
-   if (mFile.Read(mInputBuffer, 32) != 32 || mFile.Error())
-   {
-      return;
-   }
-
-   // Do we have an APE preamble?
-   if (memcmp(mInputBuffer, "APETAGEX", 8) != 0)
-   {
-      return;
-   }
-
-   // Get the (little endian) length
-   wxFileOffset len = (mInputBuffer[12] & 0xff) |
-                      ((mInputBuffer[13] & 0xff) << 8) |
-                      ((mInputBuffer[14] & 0xff) << 16) |
-                      ((mInputBuffer[15] & 0xff) << 24);
-
-   // Get needed flags
-   bool hasHeader = mInputBuffer[23] & 0x80;
-
-   // Skip the tags
-   if (!atEnd)
-   {
-      mFilePos += (32 + len);
-   }
-   else
-   {
-      mFileLen -= ((hasHeader ? 32 : 0) + len);
-   }
-}
-
-void MP3ImportFileHandle::CheckID3V1Tags()
-{
-   // Ensure file is positioned to start of (possible) tags
-   if (mFile.Seek(mFileLen - 128, wxFromStart) == wxInvalidOffset || mFile.Error())
-   {
-      return;
-   }
-
-   // An ID3v1 tag header is 3 bytes
-   if (mFile.Read(mInputBuffer, 3) != 3 || mFile.Error())
-   {
-      return;
-   }
-
-   // Do we have ID3v1 tags?
-   if (memcmp(mInputBuffer, "TAG", 3) != 0)
-   {
-      return;
-   }
-
-   // Adjust file length
-   mFileLen -= 128;
-
-   // Remember that we have tags
-   mHaveID3 = true;
-}
-
-void MP3ImportFileHandle::CheckLyrics()
-{
-   int offset = mFileLen - 9;
-
-   // Ensure file is positioned to start of (possible) lyrics
-   if (mFile.Seek(offset, wxFromStart) == wxInvalidOffset || mFile.Error())
-   {
-      return;
-   }
-
-   // An Lyrics3 footeris 9 bytes
-   if (mFile.Read(mInputBuffer, 9) != 9 || mFile.Error())
-   {
-      return;
-   }
-
-   // Found a v1 Lyrics footer?
-   if (memcmp(mInputBuffer, "LYRICSEND", 9) == 0)
-   {
-      wxFileOffset pos = wxMax(offset - 5100, 0);
-      size_t len = offset - pos;
-
-      // Ensure file is positioned to start of (possible) lyrics
-      if (mFile.Seek(pos, wxFromStart) == wxInvalidOffset || mFile.Error())
-      {
-         return;
-      }
-
-      // Read the lyrics
-      if (mFile.Read(mInputBuffer, len) != len || mFile.Error())
-      {
-         return;
-      }
-
-      // Search forward to find the beginning of the lyrics
-      for (size_t i = 0; i < len; ++i)
-      {
-         if (memcmp(&mInputBuffer[i], "LYRICSBEGIN", 11) == 0)
-         {
-            // Adjust the file length to exclude the lyrics
-            mFileLen = pos + i;
-            break;
-         }
-      }
-   }
-   // Found a v2 Lyrics footer?
-   else if (memcmp(mInputBuffer, "LYRICS200", 9) == 0)
-   {
-      // Ensure file is positioned to start of (possible) lyrics
-      if (mFile.Seek(-15, wxFromCurrent) == wxInvalidOffset || mFile.Error())
-      {
-         return;
-      }
-
-      // An Lyrics3v2 length is 6 bytes
-      if (mFile.Read(mInputBuffer, 6) != 6 || mFile.Error())
-      {
-         return;
-      }
-
-      // Adjust the file length to exclude the lyrics
-      mInputBuffer[6] = 0;
-      mFileLen -= (wxAtoi((char *) mInputBuffer) + 15);
-   }
-}
-
-void MP3ImportFileHandle::CheckID3V2Tags(bool atEnd)
-{
-   int offset = atEnd ? mFileLen - 10 : mFilePos;
-
-   // Ensure file is positioned to start of (possible) tags
-   if (mFile.Seek(offset, wxFromStart) == wxInvalidOffset || mFile.Error())
-   {
-      return;
-   }
-
-   // An ID3v2 tag header is 10 bytes
-   if (mFile.Read(mInputBuffer, 10) != 10 || mFile.Error())
-   {
-      return;
-   }
-
-   // Do we have an ID3v2 header or footer?
-   if (memcmp(mInputBuffer, atEnd ? "3DI" : "ID3", 3) != 0)
-   {
-      return;
-   }
-
-   // Get and decode the length
-   wxFileOffset len = (mInputBuffer[6] & 0x7f);
-   len = (len << 7) | (mInputBuffer[7] & 0x7f);
-   len = (len << 7) | (mInputBuffer[8] & 0x7f);
-   len = (len << 7) | (mInputBuffer[9] & 0x7f);
-
-   // Skip the tags
-   if (!atEnd)
-   {
-      mFilePos += (10 + len);
-   }
-   else
-   {
-      mFileLen -= (10 + len + 10);
-   }
-
-   // Remember that we have tags
-   mHaveID3 = true;
-}
-
-bool MP3ImportFileHandle::CheckMP3()
-{
-   wxFileOffset savedPos = mFilePos;
-
-   // Ensure file is positioned to start of 1st mp3 frame
-   if (mFile.Seek(mFilePos, wxFromStart) == wxInvalidOffset || mFile.Error())
-   {
-      return false;
-   }
-
-   // Load as much as will fit into the buffer
-   if (!FillBuffer())
-   {
-      return false;
-   }
-
-   // Initialize mad stream
-   mad_stream stream;
-   mad_stream_init(&stream);
-   mad_stream_buffer(&stream, mInputBuffer, mInputBufferLen);
-
-   // And header
-   mad_header header;
-   mad_header_init(&header);
-
-   // Scan the input buffer for 2 consecutive MP3 frames. When the header
-   // decoder finds a frame, it decodes it and ensures it is followed by
-   // another frame or EOF...thus 2 (or 1) consecutive frame(s) are detected.
-   int consecutive = 1;
-   while (consecutive > 0)
-   {
-      // Decode the header at the current stream position.
-      if (mad_header_decode(&header, &stream))
-      {
-         // End of buffer.
-         if (stream.error != MAD_ERROR_NONE)
-         {
-            break;
-         }
-      }
-
-      consecutive -= 1;
-   }
-
-   // Remember how many bytes were processed
-   int used = stream.this_frame - stream.buffer;
-
-   // Cleanup
-   mad_header_finish(&header);
-   mad_stream_finish(&stream);
-
-   // Did we find all that we wanted?
-   if (consecutive)
-   {
-      return false;
-   }
-
-   // Reset file controls
-   mInputBufferLen = 0;
-
-   // Reposition file to start of mp3 frames to prepare for the Import.
-   mFilePos = savedPos + used;
-   if (mFile.Seek(mFilePos, wxFromStart) == wxInvalidOffset || mFile.Error())
-   {
-      return false;
-   }
-
-   // Looks like an MP3...
-   return true;
-}
-
-bool MP3ImportFileHandle::FillBuffer()
-{
-   // We either want enough to fill the input buffer or what's left in the file
-   auto want = wxMin(INPUT_BUFFER_SIZE - mInputBufferLen, mFileLen - mFilePos);
-   if (want > 0)
-   {
-      // We should always get what we ask for
-      auto got = mFile.Read(&mInputBuffer[mInputBufferLen], want);
-      if (got != want || mFile.Error())
-      {
-         return false;
-      }
-
-      // Adjust input control
-      mInputBufferLen += got;
-      mFilePos += got;
-   }
-
-   // MAD requires that we add MAD_BUFFER_GUARD extra bytes when we've processed
-   // all of the MP3 frames.  Otherwise, we will drop the last frame.
-   if (mFilePos == mFileLen)
-   {
-      memset(&mInputBuffer[mInputBufferLen], 0, MAD_BUFFER_GUARD);
-      mInputBufferLen += MAD_BUFFER_GUARD;
-   }
-
-   return true;
-}
-
-void MP3ImportFileHandle::LoadID3(Tags *tags)
-{
-#ifdef USE_LIBID3TAG
-   struct id3_file *id3file = NULL;
-   auto cleanup = finally([&]
-   {
-      if (id3file)
-      {
-         id3_file_close(id3file);
-      }
-   });
-
-   // Use id3_file_fdopen() instead of id3_file_open since wxWidgets can open a
-   // file with a Unicode name and id3_file_open() can't (under Windows).
-   id3file = id3_file_fdopen(mFile.fd(), ID3_FILE_MODE_READONLY);
-   if (!id3file)
-   {
-      return;
-   }
-
-   // The file descriptor is now owned by "id3file", so we must tell "mFile" to forget
-   // about it.
-   mFile.Detach();
-
-   // Load the tags
-   struct id3_tag *id3tags = id3_file_tag(id3file);
-   if (!id3tags || id3tags->nframes == 0)
-   {
-      return;
-   }
-
-   // Convert from libid3tag's ucs4 type to wxString.
-   //
-   // The ucs4 type is unsigned long which can be 8 bytes instead
-   // of the expected 4 bytes for a UTF-32 character, so we have
-   // to convert to unsigned int and then to wxString.
-   wxMBConvUTF32 converter;
-   auto toString = [=](const id3_ucs4_t *in)
-   {
-      // Count the number of characters
-      size_t len = 0;
-      for (const id3_ucs4_t *p = in; *p; p++)
-      {
-         len++;
-      }
-
-      // Would like to use std::dynarray or runtime-sized array,
-      // but VS doesn't support either.
-      wxUint32 *buf = (wxUint32 *) alloca((len + 1) * sizeof(wxUint32));
-
-      // Copy and convert to unsigned int
-      wxUint32 *out;
-      for (out = buf; *in; in++, out++)
-      {
-         *out = (wxUint32) (*in);
-      }
-      *out = 0;
-
-      // Finally convert to and return wxString
-      return wxString((char *) buf, converter);
-   };
-
-   tags->Clear();
-
-   // Extract tags from ID3 frames and add to our tags
-   bool have_year = false;
-   for (unsigned int i = 0; i < id3tags->nframes; ++i)
-   {
-      struct id3_frame *frame = id3tags->frames[i];
-
-#if 0
-      wxLogDebug("ID: %08x '%4s'", (int) *(int *)frame->id, frame->id);
-      wxLogDebug("Desc: %s", frame->description);
-      wxLogDebug("Num fields: %d", frame->nfields);
-
-      for (unsigned int j = 0; j < frame->nfields; ++j)
-      {
-         wxLogDebug("field %d type %d", j, frame->fields[j].type);
-         if (frame->fields[j].type == ID3_FIELD_TYPE_STRINGLIST)
-         {
-            wxLogDebug("num strings %d", frame->fields[j].stringlist.nstrings);
-         }
-      }
-#endif
-
-      wxString n;
-      wxString v;
-
-      // Determine the tag name
-      if (strcmp(frame->id, ID3_FRAME_TITLE) == 0)
-      {
-         n = TAG_TITLE;
-      }
-      else if (strcmp(frame->id, ID3_FRAME_ARTIST) == 0)
-      {
-         n = TAG_ARTIST;
-      }
-      else if (strcmp(frame->id, ID3_FRAME_ALBUM) == 0)
-      {
-         n = TAG_ALBUM;
-      }
-      else if (strcmp(frame->id, ID3_FRAME_TRACK) == 0)
-      {
-         n = TAG_TRACK;
-      }
-      else if (strcmp(frame->id, ID3_FRAME_YEAR) == 0)
-      {
-         // LLL:  When libid3tag encounters the "TYER" tag, it converts it to a
-         //       "ZOBS" (obsolete) tag and adds a "TDRC" tag at the end of the
-         //       list of tags using the first 4 characters of the "TYER" tag.
-         //       Since we write both the "TDRC" and "TYER" tags, the "TDRC" tag
-         //       will always be encountered first in the list.  We want to use
-         //       it since the converted "TYER" tag may have been truncated.
-         if (have_year)
-         {
-            continue;
-         }
-         n = TAG_YEAR;
-         have_year = true;
-      }
-      else if (strcmp(frame->id, ID3_FRAME_COMMENT) == 0)
-      {
-         n = TAG_COMMENTS;
-      }
-      else if (strcmp(frame->id, ID3_FRAME_GENRE) == 0)
-      {
-         n = TAG_GENRE;
-      }
-      else
-      {
-         // Use frame description as default tag name.  The descriptions
-         // may include several "meanings" separated by "/" characters, so
-         // we just use the first meaning
-         n = UTF8CTOWX(frame->description).BeforeFirst(wxT('/'));
-      }
-
-      // Now get the tag value
-      const id3_ucs4_t *ustr = NULL;
-
-      if (n == TAG_COMMENTS)
-      {
-         ustr = id3_field_getfullstring(&frame->fields[3]);
-      }
-      else if (frame->nfields == 3)
-      {
-         ustr = id3_field_getstring(&frame->fields[1]);
-         if (ustr)
-         {
-            n = toString(ustr);
-         }
-
-         ustr = id3_field_getstring(&frame->fields[2]);
-      }
-      else if (frame->nfields >= 2)
-      {
-         ustr = id3_field_getstrings(&frame->fields[1], 0);
-      }
-
-      // Convert the value
-      if (ustr)
-      {
-         v = toString(ustr);
-      }
-
-      // And add it to the list of tags
-      if (!n.empty() && !v.empty())
-      {
-         tags->SetTag(n, v);
-      }
-   }
-
-   // Convert v1 genre to name
-   if (tags->HasTag(TAG_GENRE))
-   {
-      long g = -1;
-      if (tags->GetTag(TAG_GENRE).ToLong(&g))
-      {
-         tags->SetTag(TAG_GENRE, tags->GetGenre(g));
-      }
-   }
-#else
-   (void) tags;
-#endif
-}
-
-//
-// MAD Callbacks
-//
-
-// The input callback is called when the decoder wants more data
-mad_flow MP3ImportFileHandle::input_cb(void *that,
-                                       struct mad_stream *stream)
-{
-   auto cb = [&]()
-   {
-      return ((MP3ImportFileHandle *) that)->InputCB(stream);
-   };
-
-   return GuardedCall<mad_flow>(cb, MakeSimpleGuard(MAD_FLOW_BREAK));
-}
-
-mad_flow MP3ImportFileHandle::InputCB(struct mad_stream *stream)
-{
-   // Update the progress
-   mUpdateResult = mProgress->Update((wxLongLong_t) mFilePos, (wxLongLong_t) mFileLen);
-   if (mUpdateResult != ProgressResult::Success)
-   {
-      return MAD_FLOW_STOP;
-   }
-
-   // Stop if we've consumed all of the MP3 data
-   if (mFilePos == mFileLen)
-   {
-      return MAD_FLOW_STOP;
-   }
-
-   // "Each time you refill your buffer, you need to preserve the data in
-   // your existing buffer from stream.next_frame to the end.
-   //
-   // This usually amounts to calling memmove() on this unconsumed portion
-   // of the buffer and appending NEW data after it, before calling
-   // mad_stream_buffer()
-   //    -- Rob Leslie, on the mad-dev mailing list
-   if (stream->next_frame)
-   {
-      mInputBufferLen -= (stream->next_frame - mInputBuffer);
-      memmove(mInputBuffer, stream->next_frame, mInputBufferLen);
-   }
-
-   // Refill the buffer
-   if (!FillBuffer())
-   {
-      return MAD_FLOW_BREAK;
-   }
-
-   // And give it back to MAD
-   mad_stream_buffer(stream, mInputBuffer, mInputBufferLen);
-
-   return MAD_FLOW_CONTINUE;
-}
-
-// The filter callback lets us examine each frame and decide if it should be
-// kept or tossed.  We use this to detect the Xing or LAME tags.
-mad_flow MP3ImportFileHandle::filter_cb(void *that,
-                                        struct mad_stream const *stream,
-                                        struct mad_frame *frame)
-{
-   auto cb = [&]()
-   {
-      return ((MP3ImportFileHandle *) that)->FilterCB(stream, frame);
-   };
-
-   return GuardedCall<mad_flow>(cb, MakeSimpleGuard(MAD_FLOW_BREAK));
-}
-
-mad_flow MP3ImportFileHandle::FilterCB(struct mad_stream const *stream,
-                                       struct mad_frame *frame)
-{
-   // We only want to jinspect the first frame, so disable future calls
-   mDecoder.filter_func = nullptr;
-
-   // Is it a VBRI info frame?
-   if (memcmp(&stream->this_frame[4 + 32], "VBRI", 4) == 0)
-   {
-      mDelay = (stream->this_frame[4 + 32 + 6] & 0xff) << 8 |
-               (stream->this_frame[4 + 32 + 7] & 0xff);
-
-      return MAD_FLOW_CONTINUE;
-   }
-
-   // Look for Xing/Info information
-
-   // Get the ancillary data ptr and length. If the frame has CRC protection, we make
-   // a small adjustment to get around an apparent bug in libmad.
-   auto ptr = stream->anc_ptr.byte - (frame->header.flags & MAD_FLAG_PROTECTION ? 2 : 0);
-   int len = stream->anc_bitlen / 8;
-
-   // Ensure it's something we can understand
-   if (len < 4 || (memcmp(ptr, "Xing", 4) != 0 && memcmp(ptr, "Info", 4) != 0))
-   {
-      return MAD_FLOW_CONTINUE;
-   }
-
-   // Skip the tag
-   ptr += 4;
-   len -= 4;
-
-   enum flagBits
-   {
-      hasFrames   = 0x0001,
-      hasBytes    = 0x0002,
-      hasToc      = 0x0004,
-      hasScale    = 0x0008
-   };
-
-   // Extract the flags
-   unsigned int flags = (((((ptr[0] << 8) + ptr[1]) << 8) + ptr[2]) << 8) + ptr[3];
-   ptr += 4;
-   len -= 4;
-
-   // Skip the number of frames
-   if (len >= 4 && flags & hasFrames)
-   {
-      ptr += 4;
-      len -= 4;
-   }
-
-   // Skip the number of bytes
-   if (len >= 4 && flags & hasBytes)
-   {
-      ptr += 4;
-      len -= 4;
-   }
-
-   // Skip the TOC
-   if (len >= 100 && flags & hasToc)
-   {
-      ptr += 100;
-      len -= 100;
-   }
-
-   // Skip the VBR Scale
-   if (len >= 4 && flags & hasScale)
-   {
-      ptr += 4;
-      len -= 4;
-   }
-
-   // Bail if LAME wasn't the encoder or we don't have enough ancillary data left
-   if (len < 24 || memcmp(ptr, "LAME", 4) != 0)
-   {
-      return MAD_FLOW_IGNORE;
-   }
-
-   // Skip down to the delay and padding
-   ptr += 21;
-   len -= 21;
-
-   // Extract the delay and padding and adjust for decoder delay
-   mDelay = (ptr[0] << 4) + (ptr[1] >> 4) + MAD_DELAY;
-   mPadding = ((ptr[1] & 0x0f) << 8) + ptr[2] - MAD_DELAY;
-   if (mPadding < 0)
-   {
-      mPadding = 0;
-   }
-
-   return MAD_FLOW_IGNORE;
-}
-
-// The output callback is called every time the decoder has finished decoding
- // a frame, allowing us to use the decoded data
-mad_flow MP3ImportFileHandle::output_cb(void *that,
-                                        struct mad_header const *header,
-                                        struct mad_pcm *pcm)
-{
-   auto cb = [&]()
-   {
-      return ((MP3ImportFileHandle *) that)->OutputCB(header, pcm);
-   };
-
-   return GuardedCall<mad_flow>(cb, MakeSimpleGuard(MAD_FLOW_BREAK));
-}
-
-enum mad_flow MP3ImportFileHandle::OutputCB(struct mad_header const * WXUNUSED(header),
-                                            struct mad_pcm *pcm)
-{
-   // If this is the first run, we need to create the WaveTracks that
-   // will hold the data.  We do this now because now is the first
-   // moment when we know how many channels there are.
-   if (mChannels.empty())
-   {
-      mNumChannels = pcm->channels;
-
-      mChannels.resize(mNumChannels);
-
-      for (auto &channel: mChannels)
-      {
-         // Mad library header explains the 32 bit fixed point format with
-         // 28 fractional bits.  Effective sample format must therefore be
-         // more than 24, and this is our only choice now.
-         channel = NewWaveTrack(*mTrackFactory, floatSample, pcm->samplerate);
-      }
-   }
-
-   // Get the number of samples in each channel
-   auto samples = pcm->length;
-
-   // Convert libmad samples to float and append to WaveTracks
-   for (int chn = 0; chn < mNumChannels; ++chn)
-   {
-      // Number of samples will never be more than 1152
-      float sampleBuf[1152];
-      wxASSERT(samples <= 1152);
-
-      // Copy over the samples
-      for (int sample = 0; sample < samples; ++sample)
-      {
-         // Convert libmad's fixed point representation to float
-         sampleBuf[sample] = ((float) pcm->samples[chn][sample] / (1L << MAD_F_FRACBITS));
-      }
-
-      // And append to the channel
-      mChannels[chn]->Append(
-         (samplePtr) sampleBuf, floatSample, samples, 1,
-         // Samples were 28 bit fixed-point converted to float:
-         // see explanation above MAD_F_FRACBITS in mad.h
-         // Effective sample format must therefore be
-         // more than 24 bits, so this is our only choice.
-         floatSample);
-   }
-
-   return MAD_FLOW_CONTINUE;
-}
-
-// The error callback is used when MAD encounters an error and needs to know
-// how it should proceed
-mad_flow MP3ImportFileHandle::error_cb(void *that,
-                                       struct mad_stream *stream,
-                                       struct mad_frame *frame)
-{
-   auto cb = [&]()
-   {
-      return ((MP3ImportFileHandle *) that)->ErrorCB(stream, frame);
-   };
-
-   return GuardedCall<mad_flow>(cb, MakeSimpleGuard(MAD_FLOW_BREAK));
-}
-
-enum mad_flow MP3ImportFileHandle::ErrorCB(struct mad_stream *stream,
-                                           struct mad_frame *frame)
-{
-   // You always get a LOSTSYNC error at EOF, so just ignore it
-   if (stream->error == MAD_ERROR_LOSTSYNC && mFilePos == mFileLen)
-   {
-      return MAD_FLOW_CONTINUE;
-   }
-
-   // This can happen when parsing the first frame. We can use the number of channels
-   // to test for this since it hasn't been determined yet.
-   if (stream->error == MAD_ERROR_BADDATAPTR && mNumChannels == 0)
-   {
-      return MAD_FLOW_CONTINUE;
-   }
-
-   // Let the user know about the error
-   using namespace BasicUI;
-   ShowErrorDialog( {},
-      DefaultCaption(),
-      XO("Import failed\n\nThis is likely caused by a malformed MP3.\n\n"),
-      "Opening_malformed_MP3_files");
-   return MAD_FLOW_BREAK;
-}
-
-#endif